lecoffre 0.2.0 → 0.2.1
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/dist/lecoffre.js +856 -0
- package/package.json +5 -4
- package/bin/lecoffre.ts +0 -63
- package/src/commands/import.command.ts +0 -101
- package/src/commands/init.command.ts +0 -11
- package/src/commands/list.command.ts +0 -47
- package/src/commands/load.command.ts +0 -32
- package/src/commands/unload.command.ts +0 -32
- package/src/lib/define-argument.ts +0 -13
- package/src/lib/define-command.ts +0 -43
- package/src/lib/define-option.ts +0 -15
- package/src/lib/format.ts +0 -108
- package/src/lib/get-storage.ts +0 -11
- package/src/lib/json-storage.ts +0 -74
- package/src/lib/one-password-storage.ts +0 -233
- package/src/lib/parse-command.ts +0 -108
- package/src/lib/shell.ts +0 -52
- package/src/lib/storage.ts +0 -37
- package/src/lib/zod-utils.ts +0 -50
- package/src/options/environment.option.ts +0 -10
- package/src/options/project.option.ts +0 -13
package/package.json
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lecoffre",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Work in progress CLI project.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/hsablonniere/lecoffre"
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
|
-
"lecoffre": "./
|
|
10
|
+
"lecoffre": "./dist/lecoffre.js"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
|
-
"
|
|
14
|
-
"src",
|
|
13
|
+
"dist",
|
|
15
14
|
"README.md",
|
|
16
15
|
"LICENSE"
|
|
17
16
|
],
|
|
@@ -25,6 +24,7 @@
|
|
|
25
24
|
"@changesets/cli": "^2.29.8",
|
|
26
25
|
"@types/node": "^25.2.3",
|
|
27
26
|
"@typescript/native-preview": "^7.0.0-dev.20260217.1",
|
|
27
|
+
"esbuild": "^0.27.4",
|
|
28
28
|
"execa": "^9.6.1",
|
|
29
29
|
"oxfmt": "^0.33.0",
|
|
30
30
|
"oxlint": "^1.48.0",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"format": "oxfmt . --write",
|
|
42
42
|
"format:check": "oxfmt . --check",
|
|
43
43
|
"typecheck": "tsgo --noEmit",
|
|
44
|
+
"build": "esbuild bin/lecoffre.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/lecoffre.js",
|
|
44
45
|
"check": "pnpm lint && pnpm format:check && pnpm typecheck && pnpm test",
|
|
45
46
|
"changeset": "changeset",
|
|
46
47
|
"version-packages": "changeset version",
|
package/bin/lecoffre.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { parse } from "@bomb.sh/args";
|
|
4
|
-
import packageJson from "../package.json" with { type: "json" };
|
|
5
|
-
import { importCommand } from "../src/commands/import.command.ts";
|
|
6
|
-
import { initCommand } from "../src/commands/init.command.ts";
|
|
7
|
-
import { listCommand } from "../src/commands/list.command.ts";
|
|
8
|
-
import { loadCommand } from "../src/commands/load.command.ts";
|
|
9
|
-
import { unloadCommand } from "../src/commands/unload.command.ts";
|
|
10
|
-
import type { AnyCommandDefinition } from "../src/lib/define-command.ts";
|
|
11
|
-
import { formatCommandHelp, formatErrors, formatGlobalHelp } from "../src/lib/format.ts";
|
|
12
|
-
import {
|
|
13
|
-
CommandHelpRequested,
|
|
14
|
-
CommandValidationError,
|
|
15
|
-
parseCommand,
|
|
16
|
-
} from "../src/lib/parse-command.ts";
|
|
17
|
-
|
|
18
|
-
const { name } = packageJson;
|
|
19
|
-
const commands: Record<string, AnyCommandDefinition> = {
|
|
20
|
-
init: initCommand,
|
|
21
|
-
list: listCommand,
|
|
22
|
-
load: loadCommand,
|
|
23
|
-
unload: unloadCommand,
|
|
24
|
-
import: importCommand,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const initial = parse(process.argv.slice(2));
|
|
28
|
-
const [commandNameRaw] = initial._;
|
|
29
|
-
|
|
30
|
-
if (commandNameRaw === undefined) {
|
|
31
|
-
console.log(formatGlobalHelp(name, commands));
|
|
32
|
-
process.exit(0);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const commandName = String(commandNameRaw);
|
|
36
|
-
const command = commands[commandName];
|
|
37
|
-
|
|
38
|
-
if (command === undefined) {
|
|
39
|
-
console.error(`Unknown command "${commandName}" for "${name}"\n`);
|
|
40
|
-
console.error(formatGlobalHelp(name, commands));
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const { options, args } = parseCommand(process.argv.slice(3), command);
|
|
46
|
-
await command.handler(options, ...args);
|
|
47
|
-
} catch (error) {
|
|
48
|
-
if (error instanceof CommandHelpRequested) {
|
|
49
|
-
console.log(formatCommandHelp(name, commandName, command));
|
|
50
|
-
process.exit(0);
|
|
51
|
-
}
|
|
52
|
-
if (error instanceof CommandValidationError) {
|
|
53
|
-
console.error(formatErrors(error.errors) + "\n");
|
|
54
|
-
console.error(formatCommandHelp(name, commandName, command));
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
if (error instanceof Error) {
|
|
58
|
-
console.error(error.message);
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
61
|
-
console.error("An unexpected error occurred");
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { basename } from "node:path";
|
|
2
|
-
import { realpath } from "node:fs/promises";
|
|
3
|
-
import { parseEnv } from "node:util";
|
|
4
|
-
import { z } from "zod";
|
|
5
|
-
import { defineCommand } from "../lib/define-command.ts";
|
|
6
|
-
import { defineOption } from "../lib/define-option.ts";
|
|
7
|
-
import { getStorage } from "../lib/get-storage.ts";
|
|
8
|
-
import { environmentOption } from "../options/environment.option.ts";
|
|
9
|
-
import { projectOption } from "../options/project.option.ts";
|
|
10
|
-
import { ProjectNotFoundError } from "../lib/storage.ts";
|
|
11
|
-
|
|
12
|
-
export const importCommand = defineCommand({
|
|
13
|
-
description: "Import variables from stdin (.env format)",
|
|
14
|
-
options: {
|
|
15
|
-
project: projectOption,
|
|
16
|
-
environment: environmentOption,
|
|
17
|
-
merge: defineOption({
|
|
18
|
-
name: "merge",
|
|
19
|
-
schema: z.boolean().default(false),
|
|
20
|
-
description: "Merge with existing variables instead of replacing",
|
|
21
|
-
aliases: ["m"],
|
|
22
|
-
}),
|
|
23
|
-
},
|
|
24
|
-
async handler(options) {
|
|
25
|
-
const storage = getStorage();
|
|
26
|
-
const project = options.project ?? basename(await realpath(process.cwd()));
|
|
27
|
-
|
|
28
|
-
const input = await readStdin();
|
|
29
|
-
const newVars = parseEnv(input) as Record<string, string>;
|
|
30
|
-
|
|
31
|
-
let existingVars: Record<string, string>;
|
|
32
|
-
try {
|
|
33
|
-
const projectData = await storage.getProject(project);
|
|
34
|
-
existingVars = projectData[options.environment] ?? {};
|
|
35
|
-
} catch (error) {
|
|
36
|
-
if (error instanceof ProjectNotFoundError) {
|
|
37
|
-
existingVars = {};
|
|
38
|
-
} else {
|
|
39
|
-
throw error;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const added: Array<string> = [];
|
|
44
|
-
const updated: Array<string> = [];
|
|
45
|
-
const removed: Array<string> = [];
|
|
46
|
-
|
|
47
|
-
if (options.merge) {
|
|
48
|
-
// Merge mode: add/overwrite without clearing
|
|
49
|
-
for (const key of Object.keys(newVars)) {
|
|
50
|
-
if (key in existingVars) {
|
|
51
|
-
if (existingVars[key] !== newVars[key]) {
|
|
52
|
-
updated.push(key);
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
added.push(key);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
await storage.setVariables(project, options.environment, { ...existingVars, ...newVars });
|
|
59
|
-
} else {
|
|
60
|
-
// Replace mode: clear then set
|
|
61
|
-
for (const key of Object.keys(newVars)) {
|
|
62
|
-
if (key in existingVars) {
|
|
63
|
-
if (existingVars[key] !== newVars[key]) {
|
|
64
|
-
updated.push(key);
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
added.push(key);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
for (const key of Object.keys(existingVars)) {
|
|
71
|
-
if (!(key in newVars)) {
|
|
72
|
-
removed.push(key);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
await storage.setVariables(project, options.environment, newVars);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
for (const key of added) {
|
|
79
|
-
console.error(`+ ${key} (added)`);
|
|
80
|
-
}
|
|
81
|
-
for (const key of updated) {
|
|
82
|
-
console.error(`~ ${key} (updated)`);
|
|
83
|
-
}
|
|
84
|
-
for (const key of removed) {
|
|
85
|
-
console.error(`- ${key} (removed)`);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const totalVars = Object.keys(newVars).length;
|
|
89
|
-
console.error(
|
|
90
|
-
`Imported ${totalVars} variable${totalVars !== 1 ? "s" : ""} into ${project} [${options.environment}]`,
|
|
91
|
-
);
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
async function readStdin(): Promise<string> {
|
|
96
|
-
const chunks: Array<Buffer> = [];
|
|
97
|
-
for await (const chunk of process.stdin) {
|
|
98
|
-
chunks.push(chunk as Buffer);
|
|
99
|
-
}
|
|
100
|
-
return Buffer.concat(chunks).toString("utf-8");
|
|
101
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from "../lib/define-command.ts";
|
|
2
|
-
import { getStorage } from "../lib/get-storage.ts";
|
|
3
|
-
|
|
4
|
-
export const initCommand = defineCommand({
|
|
5
|
-
description: "Initialize the storage backend",
|
|
6
|
-
async handler() {
|
|
7
|
-
const storage = getStorage();
|
|
8
|
-
await storage.init();
|
|
9
|
-
console.log("Storage initialized.");
|
|
10
|
-
},
|
|
11
|
-
});
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from "../lib/define-command.ts";
|
|
2
|
-
import { getStorage } from "../lib/get-storage.ts";
|
|
3
|
-
import { projectOption } from "../options/project.option.ts";
|
|
4
|
-
import { ProjectNotFoundError } from "../lib/storage.ts";
|
|
5
|
-
|
|
6
|
-
export const listCommand = defineCommand({
|
|
7
|
-
description: "List projects and their environments",
|
|
8
|
-
options: {
|
|
9
|
-
project: projectOption,
|
|
10
|
-
},
|
|
11
|
-
async handler(options) {
|
|
12
|
-
const storage = getStorage();
|
|
13
|
-
|
|
14
|
-
if (options.project !== undefined) {
|
|
15
|
-
let projectData: Record<string, Record<string, string>>;
|
|
16
|
-
try {
|
|
17
|
-
projectData = await storage.getProject(options.project);
|
|
18
|
-
} catch (error) {
|
|
19
|
-
if (error instanceof ProjectNotFoundError) {
|
|
20
|
-
throw new Error(`Project not found: ${options.project}`);
|
|
21
|
-
}
|
|
22
|
-
throw error;
|
|
23
|
-
}
|
|
24
|
-
for (const [env, vars] of Object.entries(projectData)) {
|
|
25
|
-
const count = Object.keys(vars).length;
|
|
26
|
-
console.log(`${env} (${count} variable${count !== 1 ? "s" : ""})`);
|
|
27
|
-
}
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const projects = await storage.getProjects();
|
|
32
|
-
|
|
33
|
-
if (projects.length === 0) {
|
|
34
|
-
console.log("No projects found.");
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
for (const p of projects) {
|
|
39
|
-
console.log(p);
|
|
40
|
-
const projectData = await storage.getProject(p);
|
|
41
|
-
for (const [env, vars] of Object.entries(projectData)) {
|
|
42
|
-
const count = Object.keys(vars).length;
|
|
43
|
-
console.log(` ${env} (${count} variable${count !== 1 ? "s" : ""})`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
});
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { basename } from "node:path";
|
|
2
|
-
import { realpath } from "node:fs/promises";
|
|
3
|
-
import { defineCommand } from "../lib/define-command.ts";
|
|
4
|
-
import { getStorage } from "../lib/get-storage.ts";
|
|
5
|
-
import { detectShell, formatVariables } from "../lib/shell.ts";
|
|
6
|
-
import { EnvironmentNotFoundError } from "../lib/storage.ts";
|
|
7
|
-
import { environmentOption } from "../options/environment.option.ts";
|
|
8
|
-
import { projectOption } from "../options/project.option.ts";
|
|
9
|
-
|
|
10
|
-
export const loadCommand = defineCommand({
|
|
11
|
-
description: "Load variables into the current shell environment",
|
|
12
|
-
options: {
|
|
13
|
-
project: projectOption,
|
|
14
|
-
environment: environmentOption,
|
|
15
|
-
},
|
|
16
|
-
async handler(options) {
|
|
17
|
-
const storage = getStorage();
|
|
18
|
-
const project = options.project ?? basename(await realpath(process.cwd()));
|
|
19
|
-
|
|
20
|
-
const projectData = await storage.getProject(project);
|
|
21
|
-
const vars = projectData[options.environment];
|
|
22
|
-
if (vars === undefined) {
|
|
23
|
-
throw new EnvironmentNotFoundError(project, options.environment);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const shell = detectShell();
|
|
27
|
-
const output = formatVariables(shell, vars);
|
|
28
|
-
if (output !== "") {
|
|
29
|
-
console.log(output);
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
});
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { basename } from "node:path";
|
|
2
|
-
import { realpath } from "node:fs/promises";
|
|
3
|
-
import { defineCommand } from "../lib/define-command.ts";
|
|
4
|
-
import { getStorage } from "../lib/get-storage.ts";
|
|
5
|
-
import { detectShell, formatUnsetVariables } from "../lib/shell.ts";
|
|
6
|
-
import { EnvironmentNotFoundError } from "../lib/storage.ts";
|
|
7
|
-
import { environmentOption } from "../options/environment.option.ts";
|
|
8
|
-
import { projectOption } from "../options/project.option.ts";
|
|
9
|
-
|
|
10
|
-
export const unloadCommand = defineCommand({
|
|
11
|
-
description: "Unload variables from the current shell environment",
|
|
12
|
-
options: {
|
|
13
|
-
project: projectOption,
|
|
14
|
-
environment: environmentOption,
|
|
15
|
-
},
|
|
16
|
-
async handler(options) {
|
|
17
|
-
const storage = getStorage();
|
|
18
|
-
const project = options.project ?? basename(await realpath(process.cwd()));
|
|
19
|
-
|
|
20
|
-
const projectData = await storage.getProject(project);
|
|
21
|
-
const vars = projectData[options.environment];
|
|
22
|
-
if (vars === undefined) {
|
|
23
|
-
throw new EnvironmentNotFoundError(project, options.environment);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const shell = detectShell();
|
|
27
|
-
const output = formatUnsetVariables(shell, Object.keys(vars));
|
|
28
|
-
if (output !== "") {
|
|
29
|
-
console.log(output);
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { z } from "zod";
|
|
2
|
-
|
|
3
|
-
export interface ArgumentDefinition<S extends z.ZodType = z.ZodType> {
|
|
4
|
-
schema: S;
|
|
5
|
-
description: string;
|
|
6
|
-
placeholder: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function defineArgument<S extends z.ZodType>(
|
|
10
|
-
definition: ArgumentDefinition<S>,
|
|
11
|
-
): ArgumentDefinition<S> {
|
|
12
|
-
return definition;
|
|
13
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { z } from "zod";
|
|
2
|
-
import type { ArgumentDefinition } from "./define-argument.ts";
|
|
3
|
-
import type { OptionDefinition } from "./define-option.ts";
|
|
4
|
-
|
|
5
|
-
type OptionsRecord = Record<string, OptionDefinition>;
|
|
6
|
-
type ArgumentsArray = readonly ArgumentDefinition[];
|
|
7
|
-
|
|
8
|
-
type InferOptionsType<O> = O extends OptionsRecord
|
|
9
|
-
? { [K in keyof O]: z.infer<O[K]["schema"]> }
|
|
10
|
-
: Record<string, never>;
|
|
11
|
-
|
|
12
|
-
type InferArgsType<A extends ArgumentsArray> = {
|
|
13
|
-
[K in keyof A]: A[K] extends ArgumentDefinition ? z.infer<A[K]["schema"]> : never;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
type CommandHandler<O, A> = A extends ArgumentsArray
|
|
17
|
-
? (options: InferOptionsType<O>, ...args: InferArgsType<A>) => void | Promise<void>
|
|
18
|
-
: (options: InferOptionsType<O>) => void | Promise<void>;
|
|
19
|
-
|
|
20
|
-
export interface CommandDefinition<
|
|
21
|
-
O extends OptionsRecord | undefined = OptionsRecord,
|
|
22
|
-
A extends ArgumentsArray | undefined = ArgumentsArray,
|
|
23
|
-
> {
|
|
24
|
-
description: string;
|
|
25
|
-
options?: O | undefined;
|
|
26
|
-
args?: A | undefined;
|
|
27
|
-
handler: CommandHandler<O, A>;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Type-erased command definition for use in registries. */
|
|
31
|
-
export interface AnyCommandDefinition {
|
|
32
|
-
description: string;
|
|
33
|
-
options?: OptionsRecord | undefined;
|
|
34
|
-
args?: ArgumentsArray | undefined;
|
|
35
|
-
handler: (options: any, ...args: any[]) => void | Promise<void>;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function defineCommand<
|
|
39
|
-
O extends OptionsRecord | undefined = undefined,
|
|
40
|
-
A extends ArgumentsArray | undefined = undefined,
|
|
41
|
-
>(definition: CommandDefinition<O, A>): CommandDefinition<O, A> {
|
|
42
|
-
return definition;
|
|
43
|
-
}
|
package/src/lib/define-option.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { z } from "zod";
|
|
2
|
-
|
|
3
|
-
export interface OptionDefinition<S extends z.ZodType = z.ZodType> {
|
|
4
|
-
name: string;
|
|
5
|
-
schema: S;
|
|
6
|
-
description: string;
|
|
7
|
-
aliases?: Array<string>;
|
|
8
|
-
placeholder?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function defineOption<S extends z.ZodType>(
|
|
12
|
-
definition: OptionDefinition<S>,
|
|
13
|
-
): OptionDefinition<S> {
|
|
14
|
-
return definition;
|
|
15
|
-
}
|
package/src/lib/format.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { styleText } from "node:util";
|
|
2
|
-
import type { z } from "zod";
|
|
3
|
-
import type { ArgumentDefinition } from "./define-argument.ts";
|
|
4
|
-
import type { AnyCommandDefinition } from "./define-command.ts";
|
|
5
|
-
import type { OptionDefinition } from "./define-option.ts";
|
|
6
|
-
import { getDefault, isBoolean, isRequired } from "./zod-utils.ts";
|
|
7
|
-
|
|
8
|
-
export function formatGlobalHelp(toolName: string, commands: Record<string, AnyCommandDefinition>) {
|
|
9
|
-
const names = Object.keys(commands);
|
|
10
|
-
|
|
11
|
-
const sections: Array<string> = [styleText("bold", "USAGE"), ` ${toolName} <command> [options]`];
|
|
12
|
-
|
|
13
|
-
if (names.length > 0) {
|
|
14
|
-
const longest = Math.max(...names.map((n) => n.length));
|
|
15
|
-
const commandList = names
|
|
16
|
-
.map((name) => ` ${name.padEnd(longest)} ${commands[name]!.description}`)
|
|
17
|
-
.join("\n");
|
|
18
|
-
sections.push("", styleText("bold", "COMMANDS"), commandList);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return sections.join("\n");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function formatCommandHelp(
|
|
25
|
-
toolName: string,
|
|
26
|
-
commandName: string,
|
|
27
|
-
command: AnyCommandDefinition,
|
|
28
|
-
) {
|
|
29
|
-
const sections: Array<string> = [
|
|
30
|
-
styleText("bold", "USAGE"),
|
|
31
|
-
formatUsageLine(toolName, commandName, command),
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
const argList = formatArgList(command.args ?? []);
|
|
35
|
-
if (argList !== null) {
|
|
36
|
-
sections.push("", styleText("bold", "ARGUMENTS"), argList);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const optionList = formatOptionList(command.options ?? {});
|
|
40
|
-
if (optionList !== null) {
|
|
41
|
-
sections.push("", styleText("bold", "OPTIONS"), optionList);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return sections.join("\n");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function formatUsageLine(toolName: string, commandName: string, command: AnyCommandDefinition) {
|
|
48
|
-
const argPlaceholders = (command.args ?? []).map((a) => `<${a.placeholder}>`).join(" ");
|
|
49
|
-
const parts = [toolName, commandName];
|
|
50
|
-
if (argPlaceholders !== "") parts.push(argPlaceholders);
|
|
51
|
-
parts.push("[options]");
|
|
52
|
-
return ` ${parts.join(" ")}`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function formatArgList(args: readonly ArgumentDefinition[]) {
|
|
56
|
-
if (args.length === 0) return null;
|
|
57
|
-
|
|
58
|
-
const lines = args.map((arg) => ({
|
|
59
|
-
left: ` ${arg.placeholder}`,
|
|
60
|
-
description: formatArgDescription(arg.description, arg.schema),
|
|
61
|
-
}));
|
|
62
|
-
|
|
63
|
-
const longest = Math.max(...lines.map((l) => l.left.length));
|
|
64
|
-
return lines.map((l) => `${l.left.padEnd(longest)} ${l.description}`).join("\n");
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function formatOptionList(options: Record<string, OptionDefinition>) {
|
|
68
|
-
const entries = Object.values(options);
|
|
69
|
-
if (entries.length === 0) return null;
|
|
70
|
-
|
|
71
|
-
const aliasPrefixes = entries.map((opt) => {
|
|
72
|
-
const aliases = opt.aliases ?? [];
|
|
73
|
-
return aliases.length > 0 ? aliases.map((a) => `-${a}`).join(", ") + ", " : "";
|
|
74
|
-
});
|
|
75
|
-
const longestAlias = Math.max(...aliasPrefixes.map((p) => p.length));
|
|
76
|
-
|
|
77
|
-
const lines = entries.map((opt, i) => {
|
|
78
|
-
const aliasPrefix = aliasPrefixes[i]!.padStart(longestAlias);
|
|
79
|
-
const placeholder = opt.placeholder ?? opt.name;
|
|
80
|
-
const flag = isBoolean(opt.schema) ? `--${opt.name}` : `--${opt.name} <${placeholder}>`;
|
|
81
|
-
return {
|
|
82
|
-
left: ` ${aliasPrefix}${flag}`,
|
|
83
|
-
description: formatDescription(opt.description, opt.schema),
|
|
84
|
-
};
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const longest = Math.max(...lines.map((l) => l.left.length));
|
|
88
|
-
return lines.map((l) => `${l.left.padEnd(longest)} ${l.description}`).join("\n");
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function formatArgDescription(description: string, schema: z.ZodType): string {
|
|
92
|
-
const defaultValue = getDefault(schema);
|
|
93
|
-
if (defaultValue !== undefined) return `${description} (default: ${String(defaultValue)})`;
|
|
94
|
-
return isRequired(schema) ? description : `${description} (optional)`;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function formatDescription(description: string, schema: z.ZodType): string {
|
|
98
|
-
if (isRequired(schema)) return `${description} (required)`;
|
|
99
|
-
const defaultValue = getDefault(schema);
|
|
100
|
-
return defaultValue === undefined
|
|
101
|
-
? description
|
|
102
|
-
: `${description} (default: ${String(defaultValue)})`;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function formatErrors(errors: Array<string>) {
|
|
106
|
-
const errorLines = errors.map((e) => ` ${e}`).join("\n");
|
|
107
|
-
return `${styleText("bold", "ERRORS")}\n${errorLines}`;
|
|
108
|
-
}
|
package/src/lib/get-storage.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { JsonStorage } from "./json-storage.ts";
|
|
2
|
-
import { OnePasswordStorage } from "./one-password-storage.ts";
|
|
3
|
-
import type { Storage } from "./storage.ts";
|
|
4
|
-
|
|
5
|
-
export function getStorage(): Storage {
|
|
6
|
-
const storagePath = process.env.LECOFFRE_STORAGE_PATH;
|
|
7
|
-
if (storagePath !== undefined) {
|
|
8
|
-
return new JsonStorage(storagePath);
|
|
9
|
-
}
|
|
10
|
-
return new OnePasswordStorage();
|
|
11
|
-
}
|
package/src/lib/json-storage.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { ProjectNotFoundError, Storage } from "./storage.ts";
|
|
3
|
-
|
|
4
|
-
type StoreData = Record<string, Record<string, Record<string, string>>>;
|
|
5
|
-
|
|
6
|
-
export class JsonStorage extends Storage {
|
|
7
|
-
private readonly filePath: string;
|
|
8
|
-
|
|
9
|
-
constructor(filePath: string) {
|
|
10
|
-
super();
|
|
11
|
-
this.filePath = filePath;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
private async read(): Promise<StoreData> {
|
|
15
|
-
try {
|
|
16
|
-
const content = await readFile(this.filePath, "utf-8");
|
|
17
|
-
return JSON.parse(content) as StoreData;
|
|
18
|
-
} catch (error) {
|
|
19
|
-
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
20
|
-
return {};
|
|
21
|
-
}
|
|
22
|
-
throw error;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
private async write(data: StoreData): Promise<void> {
|
|
27
|
-
await writeFile(this.filePath, JSON.stringify(data, null, 2) + "\n");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async getProjects(): Promise<Array<string>> {
|
|
31
|
-
const data = await this.read();
|
|
32
|
-
return Object.keys(data);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async getProject(project: string): Promise<Record<string, Record<string, string>>> {
|
|
36
|
-
const data = await this.read();
|
|
37
|
-
const projectData = data[project];
|
|
38
|
-
if (projectData === undefined) {
|
|
39
|
-
throw new ProjectNotFoundError(project);
|
|
40
|
-
}
|
|
41
|
-
return Object.fromEntries(Object.entries(projectData).map(([env, vars]) => [env, { ...vars }]));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async init(): Promise<void> {
|
|
45
|
-
// No initialization needed for JSON storage
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async setVariables(project: string, env: string, vars: Record<string, string>): Promise<void> {
|
|
49
|
-
const data = await this.read();
|
|
50
|
-
if (data[project] === undefined) {
|
|
51
|
-
data[project] = {};
|
|
52
|
-
}
|
|
53
|
-
data[project][env] = vars;
|
|
54
|
-
await this.write(data);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async deleteEnvironment(project: string, env: string): Promise<void> {
|
|
58
|
-
const data = await this.read();
|
|
59
|
-
const projectData = data[project];
|
|
60
|
-
if (projectData !== undefined) {
|
|
61
|
-
delete projectData[env];
|
|
62
|
-
if (Object.keys(projectData).length === 0) {
|
|
63
|
-
delete data[project];
|
|
64
|
-
}
|
|
65
|
-
await this.write(data);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async deleteProject(project: string): Promise<void> {
|
|
70
|
-
const data = await this.read();
|
|
71
|
-
delete data[project];
|
|
72
|
-
await this.write(data);
|
|
73
|
-
}
|
|
74
|
-
}
|