@majikah/majik-signature 0.7.2 → 0.8.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/dist/cli-core/index.d.ts +5 -0
- package/dist/cli-core/index.js +4 -0
- package/dist/cli-core/io.d.ts +40 -0
- package/dist/cli-core/io.js +227 -0
- package/dist/cli-core/key.d.ts +37 -0
- package/dist/cli-core/key.js +165 -0
- package/dist/cli-core/operations.d.ts +22 -0
- package/dist/cli-core/operations.js +915 -0
- package/dist/cli-core/output.d.ts +9 -0
- package/dist/cli-core/output.js +88 -0
- package/dist/cli-core/types.d.ts +119 -0
- package/dist/cli-core/types.js +10 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +267 -0
- package/dist/core/embed/handlers/index.d.ts +1 -0
- package/dist/core/embed/handlers/index.js +1 -0
- package/dist/core/embed/handlers/msix.d.ts +50 -0
- package/dist/core/embed/handlers/msix.js +116 -0
- package/dist/core/embed/handlers/office.d.ts +5 -5
- package/dist/core/embed/handlers/office.js +85 -25
- package/dist/core/embed/majik-embed.js +3 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +9 -2
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/cli/io.ts
|
|
3
|
+
*
|
|
4
|
+
* Node.js filesystem helpers for the Majik Signature CLI.
|
|
5
|
+
*/
|
|
6
|
+
import type { FolderFile } from "./types";
|
|
7
|
+
/**
|
|
8
|
+
* Convert a filesystem path into the normalized relative-path convention
|
|
9
|
+
* used by MajikSignatureMap.
|
|
10
|
+
*/
|
|
11
|
+
export declare function relativePosix(root: string, target: string): string;
|
|
12
|
+
export declare function mimeTypeForPath(path: string): string;
|
|
13
|
+
export declare function isMajikArtifact(path: string): boolean;
|
|
14
|
+
export declare function pathExists(path: string): Promise<boolean>;
|
|
15
|
+
export declare function ensureDirectory(path: string): Promise<void>;
|
|
16
|
+
export declare function readBytes(path: string): Promise<Uint8Array>;
|
|
17
|
+
export declare function readText(path: string): Promise<string>;
|
|
18
|
+
export declare function readBlob(path: string): Promise<Blob>;
|
|
19
|
+
export declare function readJsonFile<T = unknown>(path: string): Promise<T>;
|
|
20
|
+
export declare function writeBytes(path: string, bytes: Uint8Array, overwrite: boolean): Promise<void>;
|
|
21
|
+
export declare function writeBlob(path: string, blob: Blob, overwrite: boolean): Promise<void>;
|
|
22
|
+
export declare function writeJsonFile(path: string, value: unknown, overwrite: boolean): Promise<void>;
|
|
23
|
+
export declare function writeText(path: string, value: string, overwrite: boolean): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Read all regular files underneath a folder into memory.
|
|
26
|
+
*
|
|
27
|
+
* This is intentional for MJKSMAP operations because the CLI may need both
|
|
28
|
+
* the file bytes and the generated manifest/bundle during a single operation.
|
|
29
|
+
*/
|
|
30
|
+
export declare function readFolderFiles(rootPath: string): Promise<FolderFile[]>;
|
|
31
|
+
export declare function getPathKind(inputPath: string): Promise<"file" | "directory">;
|
|
32
|
+
export declare function resolveCliPath(path: string): string;
|
|
33
|
+
export declare function outputPathForDetachedFile(inputPath: string): string;
|
|
34
|
+
export declare function outputPathForMap(inputDirectory: string): string;
|
|
35
|
+
export declare function outputPathForMapBundle(inputDirectory: string, mapPath: string): string;
|
|
36
|
+
export declare function getPackageVersion(): Promise<string>;
|
|
37
|
+
export declare function summarizeFiles(files: FolderFile[]): {
|
|
38
|
+
count: number;
|
|
39
|
+
bytes: number;
|
|
40
|
+
};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/cli/io.ts
|
|
3
|
+
*
|
|
4
|
+
* Node.js filesystem helpers for the Majik Signature CLI.
|
|
5
|
+
*/
|
|
6
|
+
import { access, mkdir, readFile, readdir, stat, writeFile, } from "node:fs/promises";
|
|
7
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve, } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
const MIME_TYPES = {
|
|
10
|
+
".txt": "text/plain",
|
|
11
|
+
".md": "text/markdown",
|
|
12
|
+
".csv": "text/csv",
|
|
13
|
+
".json": "application/json",
|
|
14
|
+
".xml": "application/xml",
|
|
15
|
+
".html": "text/html",
|
|
16
|
+
".htm": "text/html",
|
|
17
|
+
".pdf": "application/pdf",
|
|
18
|
+
".png": "image/png",
|
|
19
|
+
".jpg": "image/jpeg",
|
|
20
|
+
".jpeg": "image/jpeg",
|
|
21
|
+
".webp": "image/webp",
|
|
22
|
+
".gif": "image/gif",
|
|
23
|
+
".mp3": "audio/mpeg",
|
|
24
|
+
".wav": "audio/wav",
|
|
25
|
+
".flac": "audio/flac",
|
|
26
|
+
".mp4": "video/mp4",
|
|
27
|
+
".mkv": "video/x-matroska",
|
|
28
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
29
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
30
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
31
|
+
".zip": "application/zip",
|
|
32
|
+
".exe": "application/vnd.microsoft.portable-executable",
|
|
33
|
+
".msi": "application/x-msi",
|
|
34
|
+
".msix": "application/msix",
|
|
35
|
+
".deb": "application/vnd.debian.binary-package",
|
|
36
|
+
".mjksig": "application/vnd.majikah.mjksig",
|
|
37
|
+
".mjksmap": "application/vnd.majikah.mjksmap",
|
|
38
|
+
};
|
|
39
|
+
const MAJIK_ARTIFACT_EXTENSIONS = new Set([".mjksig", ".mjksmap"]);
|
|
40
|
+
function normalizeSlashes(value) {
|
|
41
|
+
return value.replaceAll("\\", "/");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Convert a filesystem path into the normalized relative-path convention
|
|
45
|
+
* used by MajikSignatureMap.
|
|
46
|
+
*/
|
|
47
|
+
export function relativePosix(root, target) {
|
|
48
|
+
return normalizeSlashes(relative(root, target)).replace(/^\/+/, "").trim();
|
|
49
|
+
}
|
|
50
|
+
export function mimeTypeForPath(path) {
|
|
51
|
+
return MIME_TYPES[extname(path).toLowerCase()] ?? "application/octet-stream";
|
|
52
|
+
}
|
|
53
|
+
export function isMajikArtifact(path) {
|
|
54
|
+
return MAJIK_ARTIFACT_EXTENSIONS.has(extname(path).toLowerCase());
|
|
55
|
+
}
|
|
56
|
+
export async function pathExists(path) {
|
|
57
|
+
try {
|
|
58
|
+
await access(path);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export async function ensureDirectory(path) {
|
|
66
|
+
await mkdir(path, {
|
|
67
|
+
recursive: true,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
export async function readBytes(path) {
|
|
71
|
+
return new Uint8Array(await readFile(path));
|
|
72
|
+
}
|
|
73
|
+
export async function readText(path) {
|
|
74
|
+
return readFile(path, "utf8");
|
|
75
|
+
}
|
|
76
|
+
export async function readBlob(path) {
|
|
77
|
+
const bytes = await readBytes(path);
|
|
78
|
+
return new Blob([bytes], {
|
|
79
|
+
type: mimeTypeForPath(path),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
export async function readJsonFile(path) {
|
|
83
|
+
const text = await readText(path);
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(text);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
throw new Error(`Invalid JSON in "${path}": ${error instanceof Error ? error.message : String(error)}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export async function writeBytes(path, bytes, overwrite) {
|
|
92
|
+
if (!overwrite && (await pathExists(path))) {
|
|
93
|
+
throw new Error(`Output already exists: "${path}". Use --overwrite to replace it.`);
|
|
94
|
+
}
|
|
95
|
+
await ensureDirectory(dirname(path));
|
|
96
|
+
await writeFile(path, bytes);
|
|
97
|
+
}
|
|
98
|
+
export async function writeBlob(path, blob, overwrite) {
|
|
99
|
+
const bytes = new Uint8Array(await blob.arrayBuffer());
|
|
100
|
+
await writeBytes(path, bytes, overwrite);
|
|
101
|
+
}
|
|
102
|
+
export async function writeJsonFile(path, value, overwrite) {
|
|
103
|
+
const json = JSON.stringify(value, null, 2) + "\n";
|
|
104
|
+
await writeText(path, json, overwrite);
|
|
105
|
+
}
|
|
106
|
+
export async function writeText(path, value, overwrite) {
|
|
107
|
+
if (!overwrite && (await pathExists(path))) {
|
|
108
|
+
throw new Error(`Output already exists: "${path}". Use --overwrite to replace it.`);
|
|
109
|
+
}
|
|
110
|
+
await ensureDirectory(dirname(path));
|
|
111
|
+
await writeFile(path, value, "utf8");
|
|
112
|
+
}
|
|
113
|
+
async function walkDirectory(root, current, output) {
|
|
114
|
+
const entries = await readdir(current, {
|
|
115
|
+
withFileTypes: true,
|
|
116
|
+
});
|
|
117
|
+
for (const entry of entries) {
|
|
118
|
+
const absolutePath = join(current, entry.name);
|
|
119
|
+
/**
|
|
120
|
+
* Do not follow symlinks. This avoids directory cycles and prevents a
|
|
121
|
+
* folder signing operation from unexpectedly escaping its input tree.
|
|
122
|
+
*/
|
|
123
|
+
if (entry.isSymbolicLink()) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (entry.isDirectory()) {
|
|
127
|
+
await walkDirectory(root, absolutePath, output);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (!entry.isFile()) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Existing Majik Signature sidecars/manifests are artifacts, not source
|
|
135
|
+
* files for a subsequent folder operation.
|
|
136
|
+
*/
|
|
137
|
+
if (isMajikArtifact(absolutePath)) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const fileStat = await stat(absolutePath);
|
|
141
|
+
output.push({
|
|
142
|
+
absolutePath,
|
|
143
|
+
relativePath: relativePosix(root, absolutePath),
|
|
144
|
+
blob: await readBlob(absolutePath),
|
|
145
|
+
size: fileStat.size,
|
|
146
|
+
mimeType: mimeTypeForPath(absolutePath),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Read all regular files underneath a folder into memory.
|
|
152
|
+
*
|
|
153
|
+
* This is intentional for MJKSMAP operations because the CLI may need both
|
|
154
|
+
* the file bytes and the generated manifest/bundle during a single operation.
|
|
155
|
+
*/
|
|
156
|
+
export async function readFolderFiles(rootPath) {
|
|
157
|
+
const root = resolve(rootPath);
|
|
158
|
+
const rootStat = await stat(root);
|
|
159
|
+
if (!rootStat.isDirectory()) {
|
|
160
|
+
throw new Error(`Expected a directory: "${root}"`);
|
|
161
|
+
}
|
|
162
|
+
const result = [];
|
|
163
|
+
await walkDirectory(root, root, result);
|
|
164
|
+
result.sort((a, b) => a.relativePath.localeCompare(b.relativePath, undefined, {
|
|
165
|
+
sensitivity: "base",
|
|
166
|
+
}));
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
export async function getPathKind(inputPath) {
|
|
170
|
+
const target = resolve(inputPath);
|
|
171
|
+
let fileStat;
|
|
172
|
+
try {
|
|
173
|
+
fileStat = await stat(target);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
throw new Error(`Path does not exist: "${target}"`);
|
|
177
|
+
}
|
|
178
|
+
if (fileStat.isDirectory()) {
|
|
179
|
+
return "directory";
|
|
180
|
+
}
|
|
181
|
+
if (fileStat.isFile()) {
|
|
182
|
+
return "file";
|
|
183
|
+
}
|
|
184
|
+
throw new Error(`Unsupported filesystem object: "${target}"`);
|
|
185
|
+
}
|
|
186
|
+
export function resolveCliPath(path) {
|
|
187
|
+
if (isAbsolute(path)) {
|
|
188
|
+
return resolve(path);
|
|
189
|
+
}
|
|
190
|
+
return resolve(process.cwd(), path);
|
|
191
|
+
}
|
|
192
|
+
export function outputPathForDetachedFile(inputPath) {
|
|
193
|
+
return `${inputPath}.mjksig`;
|
|
194
|
+
}
|
|
195
|
+
export function outputPathForMap(inputDirectory) {
|
|
196
|
+
return join(inputDirectory, "signatures.mjksmap");
|
|
197
|
+
}
|
|
198
|
+
export function outputPathForMapBundle(inputDirectory, mapPath) {
|
|
199
|
+
const directoryName = basename(inputDirectory);
|
|
200
|
+
const mapName = basename(mapPath, extname(mapPath));
|
|
201
|
+
return join(dirname(mapPath), `${directoryName || mapName}.mjksbundle.zip`);
|
|
202
|
+
}
|
|
203
|
+
export async function getPackageVersion() {
|
|
204
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
205
|
+
/**
|
|
206
|
+
* dist/cli/io.js
|
|
207
|
+
* ↑ ../
|
|
208
|
+
* dist/cli
|
|
209
|
+
* ↑ ../
|
|
210
|
+
* dist
|
|
211
|
+
* ↑ package root
|
|
212
|
+
*/
|
|
213
|
+
const packageJsonPath = resolve(dirname(currentFile), "../../package.json");
|
|
214
|
+
try {
|
|
215
|
+
const packageJson = await readJsonFile(packageJsonPath);
|
|
216
|
+
return packageJson.version ?? "unknown";
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return "unknown";
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
export function summarizeFiles(files) {
|
|
223
|
+
return {
|
|
224
|
+
count: files.length,
|
|
225
|
+
bytes: files.reduce((total, file) => total + file.size, 0),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/cli/key.ts
|
|
3
|
+
*
|
|
4
|
+
* Majik Key loading/unlocking for the npm CLI.
|
|
5
|
+
*
|
|
6
|
+
* The npm CLI intentionally consumes explicit JSON key backups instead of
|
|
7
|
+
* reaching into the desktop application's account store.
|
|
8
|
+
*/
|
|
9
|
+
import { MajikKey } from "@majikah/majik-key";
|
|
10
|
+
export type LoadedMajikKey = MajikKey;
|
|
11
|
+
/**
|
|
12
|
+
* Read a passphrase without echoing it to the terminal.
|
|
13
|
+
*
|
|
14
|
+
* On Windows and Unix terminals we use raw TTY mode rather than relying on
|
|
15
|
+
* readline's normal line editor, which would echo characters.
|
|
16
|
+
*/
|
|
17
|
+
export declare function promptForPassphrase(message?: string): Promise<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Load a MajikKey without unlocking it.
|
|
20
|
+
*
|
|
21
|
+
* This is used by verification and allowlist construction because those
|
|
22
|
+
* operations only need public-key material.
|
|
23
|
+
*/
|
|
24
|
+
export declare function loadMajikKey(keyPath: string): Promise<LoadedMajikKey>;
|
|
25
|
+
/**
|
|
26
|
+
* Load and unlock a signing-capable MajikKey.
|
|
27
|
+
*
|
|
28
|
+
* Passphrase resolution order:
|
|
29
|
+
*
|
|
30
|
+
* 1. MAJIK_KEY_PASSPHRASE
|
|
31
|
+
* 2. interactive hidden terminal prompt
|
|
32
|
+
*/
|
|
33
|
+
export declare function loadSigningKey(keyPath: string): Promise<LoadedMajikKey>;
|
|
34
|
+
/**
|
|
35
|
+
* Explicitly lock a key after the operation.
|
|
36
|
+
*/
|
|
37
|
+
export declare function lockKey(key: LoadedMajikKey): void;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/cli/key.ts
|
|
3
|
+
*
|
|
4
|
+
* Majik Key loading/unlocking for the npm CLI.
|
|
5
|
+
*
|
|
6
|
+
* The npm CLI intentionally consumes explicit JSON key backups instead of
|
|
7
|
+
* reaching into the desktop application's account store.
|
|
8
|
+
*/
|
|
9
|
+
import { createInterface } from "node:readline/promises";
|
|
10
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
11
|
+
import { MajikKey } from "@majikah/majik-key";
|
|
12
|
+
import { readJsonFile, resolveCliPath } from "./io";
|
|
13
|
+
/**
|
|
14
|
+
* Read a passphrase without echoing it to the terminal.
|
|
15
|
+
*
|
|
16
|
+
* On Windows and Unix terminals we use raw TTY mode rather than relying on
|
|
17
|
+
* readline's normal line editor, which would echo characters.
|
|
18
|
+
*/
|
|
19
|
+
export async function promptForPassphrase(message = "Majik Key passphrase: ") {
|
|
20
|
+
/**
|
|
21
|
+
* Non-interactive automation must take precedence over TTY detection.
|
|
22
|
+
*
|
|
23
|
+
* Vitest, CI, npm scripts, pipes, and other non-interactive environments
|
|
24
|
+
* do not expose stdin/stdout as TTYs, but MAJIK_KEY_PASSPHRASE is explicitly
|
|
25
|
+
* supported for exactly these cases.
|
|
26
|
+
*/
|
|
27
|
+
const envPassphrase = process.env.MAJIK_KEY_PASSPHRASE;
|
|
28
|
+
if (envPassphrase !== undefined) {
|
|
29
|
+
return envPassphrase;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Only require a TTY when we actually need to ask the user interactively.
|
|
33
|
+
*/
|
|
34
|
+
if (!input.isTTY || !output.isTTY) {
|
|
35
|
+
throw new Error("A passphrase is required, but stdin is not an interactive terminal. " +
|
|
36
|
+
"Set MAJIK_KEY_PASSPHRASE for non-interactive use.");
|
|
37
|
+
}
|
|
38
|
+
const rl = createInterface({
|
|
39
|
+
input,
|
|
40
|
+
output,
|
|
41
|
+
});
|
|
42
|
+
/**
|
|
43
|
+
* readline does not provide a portable hidden-input switch.
|
|
44
|
+
* Temporarily switch stdin to raw mode and collect characters ourselves.
|
|
45
|
+
*/
|
|
46
|
+
rl.pause();
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const stdin = input;
|
|
49
|
+
let value = "";
|
|
50
|
+
let cleanedUp = false;
|
|
51
|
+
const cleanup = () => {
|
|
52
|
+
if (cleanedUp)
|
|
53
|
+
return;
|
|
54
|
+
cleanedUp = true;
|
|
55
|
+
stdin.setRawMode?.(false);
|
|
56
|
+
stdin.pause();
|
|
57
|
+
stdin.removeListener("data", onData);
|
|
58
|
+
try {
|
|
59
|
+
rl.close();
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Cleanup only.
|
|
63
|
+
}
|
|
64
|
+
process.stdout.write("\n");
|
|
65
|
+
};
|
|
66
|
+
const onData = (chunk) => {
|
|
67
|
+
const text = chunk.toString("utf8");
|
|
68
|
+
for (const character of text) {
|
|
69
|
+
if (character === "\u0003") {
|
|
70
|
+
cleanup();
|
|
71
|
+
reject(new Error("Operation cancelled."));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (character === "\r" || character === "\n") {
|
|
75
|
+
const result = value;
|
|
76
|
+
cleanup();
|
|
77
|
+
resolve(result);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (character === "\u0008" || character === "\u007f") {
|
|
81
|
+
value = value.slice(0, -1);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
value += character;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
try {
|
|
88
|
+
process.stdout.write(message);
|
|
89
|
+
stdin.setEncoding("utf8");
|
|
90
|
+
stdin.setRawMode?.(true);
|
|
91
|
+
stdin.resume();
|
|
92
|
+
stdin.on("data", onData);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
cleanup();
|
|
96
|
+
reject(error);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function assertSupportedKeyPath(path) {
|
|
101
|
+
const lower = path.toLowerCase();
|
|
102
|
+
if (lower.endsWith(".json")) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (lower.endsWith(".png")) {
|
|
106
|
+
throw new Error("PNG Majik Key backups are not supported by the current npm SDK CLI " +
|
|
107
|
+
"because @majikah/majik-key does not currently expose the PNG-backup " +
|
|
108
|
+
"restore API used by the desktop application. Use a .json backup.");
|
|
109
|
+
}
|
|
110
|
+
throw new Error(`Unsupported Majik Key file "${path}". Expected a .json backup.`);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Load a MajikKey without unlocking it.
|
|
114
|
+
*
|
|
115
|
+
* This is used by verification and allowlist construction because those
|
|
116
|
+
* operations only need public-key material.
|
|
117
|
+
*/
|
|
118
|
+
export async function loadMajikKey(keyPath) {
|
|
119
|
+
const resolvedPath = resolveCliPath(keyPath);
|
|
120
|
+
assertSupportedKeyPath(resolvedPath);
|
|
121
|
+
const json = await readJsonFile(resolvedPath);
|
|
122
|
+
try {
|
|
123
|
+
return MajikKey.fromJSON(json);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
throw new Error(`Failed to load Majik Key "${resolvedPath}": ${error instanceof Error ? error.message : String(error)}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Load and unlock a signing-capable MajikKey.
|
|
131
|
+
*
|
|
132
|
+
* Passphrase resolution order:
|
|
133
|
+
*
|
|
134
|
+
* 1. MAJIK_KEY_PASSPHRASE
|
|
135
|
+
* 2. interactive hidden terminal prompt
|
|
136
|
+
*/
|
|
137
|
+
export async function loadSigningKey(keyPath) {
|
|
138
|
+
const key = await loadMajikKey(keyPath);
|
|
139
|
+
if (!key.hasSigningKeys) {
|
|
140
|
+
throw new Error("The supplied Majik Key does not contain Ed25519 + ML-DSA-87 signing keys.");
|
|
141
|
+
}
|
|
142
|
+
if (!key.isUnlocked) {
|
|
143
|
+
const passphrase = await promptForPassphrase();
|
|
144
|
+
await key.unlock(passphrase);
|
|
145
|
+
}
|
|
146
|
+
if (!key.isUnlocked) {
|
|
147
|
+
throw new Error("Majik Key remained locked after unlock.");
|
|
148
|
+
}
|
|
149
|
+
return key;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Explicitly lock a key after the operation.
|
|
153
|
+
*/
|
|
154
|
+
export function lockKey(key) {
|
|
155
|
+
try {
|
|
156
|
+
if (key.isUnlocked) {
|
|
157
|
+
key.lock();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
/**
|
|
162
|
+
* Cleanup should never replace the original operation result/error.
|
|
163
|
+
*/
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/cli/operations.ts
|
|
3
|
+
*
|
|
4
|
+
* High-level filesystem operations for the Majik Signature CLI.
|
|
5
|
+
*
|
|
6
|
+
* This file intentionally does NOT reproduce the desktop application's
|
|
7
|
+
* CliRequest / IPC architecture.
|
|
8
|
+
*
|
|
9
|
+
* CLI
|
|
10
|
+
* ↓
|
|
11
|
+
* typed options
|
|
12
|
+
* ↓
|
|
13
|
+
* existing MajikSignature SDK
|
|
14
|
+
* ↓
|
|
15
|
+
* filesystem
|
|
16
|
+
*/
|
|
17
|
+
import type { CanSignCliOptions, CliResult, InspectCliOptions, SealCliOptions, SignCliOptions, VerifyCliOptions } from "./types.js";
|
|
18
|
+
export declare function runSign(options: SignCliOptions): Promise<CliResult>;
|
|
19
|
+
export declare function runVerify(options: VerifyCliOptions): Promise<CliResult>;
|
|
20
|
+
export declare function runSeal(options: SealCliOptions): Promise<CliResult>;
|
|
21
|
+
export declare function runCanSign(options: CanSignCliOptions): Promise<CliResult>;
|
|
22
|
+
export declare function runInspect(options: InspectCliOptions): Promise<CliResult>;
|