toolcraft-openapi 0.0.50 → 0.0.51
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/node_modules/@poe-code/frontmatter/dist/fences.d.ts +17 -0
- package/node_modules/@poe-code/frontmatter/dist/fences.js +33 -5
- package/node_modules/@poe-code/frontmatter/dist/parse.js +86 -14
- package/node_modules/@poe-code/frontmatter/dist/stringify.js +13 -0
- package/node_modules/auth-store/dist/create-secret-store.js +4 -3
- package/node_modules/auth-store/dist/encrypted-file-store.js +49 -2
- package/node_modules/auth-store/dist/keychain-store.js +11 -4
- package/package.json +2 -2
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
export type FrontmatterBlock = {
|
|
2
2
|
raw: string;
|
|
3
3
|
body: string;
|
|
4
|
+
rawStart: number;
|
|
5
|
+
rawEnd: number;
|
|
4
6
|
};
|
|
5
7
|
export type SplitFrontmatterResult = FrontmatterBlock | {
|
|
6
8
|
body: string;
|
|
7
9
|
};
|
|
10
|
+
export type InspectFrontmatterResult = (FrontmatterBlock & {
|
|
11
|
+
kind: "frontmatter";
|
|
12
|
+
}) | {
|
|
13
|
+
kind: "missing-closing-fence";
|
|
14
|
+
raw: string;
|
|
15
|
+
rawStart: number;
|
|
16
|
+
rawEnd: number;
|
|
17
|
+
body: "";
|
|
18
|
+
message: string;
|
|
19
|
+
position: number;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "body";
|
|
22
|
+
body: string;
|
|
23
|
+
};
|
|
8
24
|
export declare function splitFrontmatterBlock(source: string): SplitFrontmatterResult;
|
|
25
|
+
export declare function inspectFrontmatterBlock(source: string): InspectFrontmatterResult;
|
|
@@ -1,15 +1,43 @@
|
|
|
1
|
+
const MISSING_END_DELIMITER_MESSAGE = "Missing YAML frontmatter end delimiter (---).";
|
|
1
2
|
export function splitFrontmatterBlock(source) {
|
|
3
|
+
const inspected = inspectFrontmatterBlock(source);
|
|
4
|
+
if (inspected.kind === "body") {
|
|
5
|
+
return { body: inspected.body };
|
|
6
|
+
}
|
|
7
|
+
if (inspected.kind === "missing-closing-fence") {
|
|
8
|
+
throw new Error(inspected.message);
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
raw: inspected.raw,
|
|
12
|
+
rawStart: inspected.rawStart,
|
|
13
|
+
rawEnd: inspected.rawEnd,
|
|
14
|
+
body: inspected.body
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function inspectFrontmatterBlock(source) {
|
|
2
18
|
const content = source.startsWith("\uFEFF") ? source.slice(1) : source;
|
|
19
|
+
const sourceOffset = source.length - content.length;
|
|
3
20
|
const opening = readOpeningFence(content);
|
|
4
21
|
if (opening === undefined) {
|
|
5
|
-
return { body: source };
|
|
22
|
+
return { kind: "body", body: source };
|
|
6
23
|
}
|
|
7
24
|
const closing = findClosingFence(content, opening.next);
|
|
8
25
|
if (closing === undefined) {
|
|
9
|
-
|
|
26
|
+
return {
|
|
27
|
+
kind: "missing-closing-fence",
|
|
28
|
+
raw: content.slice(opening.next),
|
|
29
|
+
rawStart: sourceOffset + opening.next,
|
|
30
|
+
rawEnd: source.length,
|
|
31
|
+
body: "",
|
|
32
|
+
message: MISSING_END_DELIMITER_MESSAGE,
|
|
33
|
+
position: source.length
|
|
34
|
+
};
|
|
10
35
|
}
|
|
11
36
|
return {
|
|
37
|
+
kind: "frontmatter",
|
|
12
38
|
raw: content.slice(opening.next, closing.index),
|
|
39
|
+
rawStart: sourceOffset + opening.next,
|
|
40
|
+
rawEnd: sourceOffset + closing.index,
|
|
13
41
|
body: content.slice(closing.end + closing.lineBreakLength)
|
|
14
42
|
};
|
|
15
43
|
}
|
|
@@ -18,7 +46,7 @@ function readOpeningFence(source) {
|
|
|
18
46
|
return undefined;
|
|
19
47
|
}
|
|
20
48
|
const lineEnd = findLineEnd(source, 0);
|
|
21
|
-
if (lineEnd.lineBreakLength === 0 || source.slice(0, lineEnd.index)
|
|
49
|
+
if (lineEnd.lineBreakLength === 0 || !isFenceLine(source.slice(0, lineEnd.index))) {
|
|
22
50
|
return undefined;
|
|
23
51
|
}
|
|
24
52
|
return { next: lineEnd.index + lineEnd.lineBreakLength };
|
|
@@ -27,7 +55,7 @@ function findClosingFence(source, start) {
|
|
|
27
55
|
let lineStart = start;
|
|
28
56
|
while (lineStart <= source.length) {
|
|
29
57
|
const lineEnd = findLineEnd(source, lineStart);
|
|
30
|
-
if (
|
|
58
|
+
if (isFenceLine(source.slice(lineStart, lineEnd.index))) {
|
|
31
59
|
return {
|
|
32
60
|
index: lineStart,
|
|
33
61
|
end: lineEnd.index,
|
|
@@ -41,7 +69,7 @@ function findClosingFence(source, start) {
|
|
|
41
69
|
}
|
|
42
70
|
return undefined;
|
|
43
71
|
}
|
|
44
|
-
function
|
|
72
|
+
function isFenceLine(line) {
|
|
45
73
|
if (!line.startsWith("---")) {
|
|
46
74
|
return false;
|
|
47
75
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LineCounter, parse, parseDocument } from "yaml";
|
|
2
|
-
import { splitFrontmatterBlock } from "./fences.js";
|
|
2
|
+
import { inspectFrontmatterBlock, splitFrontmatterBlock } from "./fences.js";
|
|
3
3
|
export class FrontmatterParseError extends Error {
|
|
4
4
|
constructor(message) {
|
|
5
5
|
super(message);
|
|
@@ -20,9 +20,9 @@ export function parseFrontmatter(source, options = {}) {
|
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
export function parseFrontmatterDocument(source, options = {}) {
|
|
23
|
-
const split =
|
|
24
|
-
const lineCounter =
|
|
25
|
-
if (split.
|
|
23
|
+
const split = inspectFrontmatterBlock(source);
|
|
24
|
+
const lineCounter = createSourceLineCounter(source);
|
|
25
|
+
if (split.kind === "body") {
|
|
26
26
|
return {
|
|
27
27
|
frontmatter: {},
|
|
28
28
|
body: split.body,
|
|
@@ -30,14 +30,26 @@ export function parseFrontmatterDocument(source, options = {}) {
|
|
|
30
30
|
lineCounter
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
if (split.kind === "missing-closing-fence") {
|
|
34
|
+
return {
|
|
35
|
+
frontmatter: {},
|
|
36
|
+
body: split.body,
|
|
37
|
+
errors: [{ message: split.message, pos: [split.position, split.position] }],
|
|
38
|
+
lineCounter
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const yamlLineCounter = new LineCounter();
|
|
42
|
+
const normalizedYaml = normalizeYamlLineEndings(split.raw);
|
|
43
|
+
const document = parseDocument(normalizedYaml, {
|
|
44
|
+
lineCounter: yamlLineCounter,
|
|
35
45
|
prettyErrors: false,
|
|
36
46
|
uniqueKeys: options.uniqueKeys ?? false
|
|
37
47
|
});
|
|
38
48
|
const errors = document.errors.map((error) => ({
|
|
39
49
|
message: error.message,
|
|
40
|
-
...(error.pos === undefined
|
|
50
|
+
...(error.pos === undefined
|
|
51
|
+
? {}
|
|
52
|
+
: { pos: translateYamlErrorPosition(error.pos, split) })
|
|
41
53
|
}));
|
|
42
54
|
if (errors.length > 0) {
|
|
43
55
|
return {
|
|
@@ -47,12 +59,72 @@ export function parseFrontmatterDocument(source, options = {}) {
|
|
|
47
59
|
lineCounter
|
|
48
60
|
};
|
|
49
61
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
try {
|
|
63
|
+
return {
|
|
64
|
+
frontmatter: normalizeYamlFrontmatter(document.toJSON()),
|
|
65
|
+
body: split.body,
|
|
66
|
+
errors,
|
|
67
|
+
lineCounter
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error instanceof FrontmatterParseError) {
|
|
72
|
+
return {
|
|
73
|
+
frontmatter: {},
|
|
74
|
+
body: split.body,
|
|
75
|
+
errors: [{ message: error.message }],
|
|
76
|
+
lineCounter
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function createSourceLineCounter(source) {
|
|
83
|
+
const lineCounter = new LineCounter();
|
|
84
|
+
lineCounter.addNewLine(0);
|
|
85
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
86
|
+
const character = source[index];
|
|
87
|
+
if (character === "\n") {
|
|
88
|
+
lineCounter.addNewLine(index + 1);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (character === "\r") {
|
|
92
|
+
lineCounter.addNewLine(index + (source[index + 1] === "\n" ? 2 : 1));
|
|
93
|
+
if (source[index + 1] === "\n") {
|
|
94
|
+
index += 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return lineCounter;
|
|
99
|
+
}
|
|
100
|
+
function translateYamlErrorPosition(pos, split) {
|
|
101
|
+
return pos.map((offset) => split.rawStart + normalizeYamlErrorOffset(split.raw, offset));
|
|
102
|
+
}
|
|
103
|
+
function normalizeYamlErrorOffset(raw, offset) {
|
|
104
|
+
if (offset >= raw.length && endsWithLineBreak(raw)) {
|
|
105
|
+
return findPreviousLineStart(raw, raw.length);
|
|
106
|
+
}
|
|
107
|
+
return offset;
|
|
108
|
+
}
|
|
109
|
+
function endsWithLineBreak(value) {
|
|
110
|
+
return value.endsWith("\n") || value.endsWith("\r");
|
|
111
|
+
}
|
|
112
|
+
function findPreviousLineStart(value, end) {
|
|
113
|
+
let index = end - 1;
|
|
114
|
+
if (value[index] === "\n") {
|
|
115
|
+
index -= 1;
|
|
116
|
+
}
|
|
117
|
+
if (value[index] === "\r") {
|
|
118
|
+
index -= 1;
|
|
119
|
+
}
|
|
120
|
+
while (index >= 0) {
|
|
121
|
+
const character = value[index];
|
|
122
|
+
if (character === "\n" || character === "\r") {
|
|
123
|
+
return index + 1;
|
|
124
|
+
}
|
|
125
|
+
index -= 1;
|
|
126
|
+
}
|
|
127
|
+
return 0;
|
|
56
128
|
}
|
|
57
129
|
function splitFrontmatter(source) {
|
|
58
130
|
try {
|
|
@@ -102,7 +174,7 @@ function normalizeYamlFrontmatter(value) {
|
|
|
102
174
|
if (value === null || value === undefined) {
|
|
103
175
|
return {};
|
|
104
176
|
}
|
|
105
|
-
if (!
|
|
177
|
+
if (!isPlainRecord(value)) {
|
|
106
178
|
throw new FrontmatterParseError("YAML frontmatter must parse to an object.");
|
|
107
179
|
}
|
|
108
180
|
return normalizeYamlValue(value);
|
|
@@ -2,6 +2,7 @@ import { stringify } from "yaml";
|
|
|
2
2
|
import { FrontmatterParseError } from "./parse.js";
|
|
3
3
|
export function stringifyFrontmatter(frontmatter, body) {
|
|
4
4
|
try {
|
|
5
|
+
assertFrontmatterRoot(frontmatter);
|
|
5
6
|
assertAcyclic(frontmatter);
|
|
6
7
|
return `---\n${stringify(frontmatter, { aliasDuplicateObjects: false }).trimEnd()}\n---\n${body}`;
|
|
7
8
|
}
|
|
@@ -13,6 +14,11 @@ export function stringifyFrontmatter(frontmatter, body) {
|
|
|
13
14
|
throw new FrontmatterParseError(`Invalid YAML frontmatter: ${message}`);
|
|
14
15
|
}
|
|
15
16
|
}
|
|
17
|
+
function assertFrontmatterRoot(value) {
|
|
18
|
+
if (!isPlainRecord(value)) {
|
|
19
|
+
throw new FrontmatterParseError("YAML frontmatter must parse to an object.");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
16
22
|
function assertAcyclic(value, seen = new WeakSet()) {
|
|
17
23
|
if (typeof value !== "object" || value === null) {
|
|
18
24
|
return;
|
|
@@ -33,3 +39,10 @@ function assertAcyclic(value, seen = new WeakSet()) {
|
|
|
33
39
|
}
|
|
34
40
|
seen.delete(value);
|
|
35
41
|
}
|
|
42
|
+
function isPlainRecord(value) {
|
|
43
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const prototype = Object.getPrototypeOf(value);
|
|
47
|
+
return prototype === Object.prototype || prototype === null;
|
|
48
|
+
}
|
|
@@ -28,13 +28,14 @@ export function createSecretStore(input) {
|
|
|
28
28
|
function resolveBackend(input) {
|
|
29
29
|
const envVar = input.backendEnvVar ?? DEFAULT_BACKEND_ENV_VAR;
|
|
30
30
|
const configuredBackend = input.backend ?? getOwnEnvValue(input.env, envVar) ?? getOwnEnvValue(process.env, envVar);
|
|
31
|
-
|
|
31
|
+
const backend = configuredBackend?.trim();
|
|
32
|
+
if (backend === "keychain") {
|
|
32
33
|
return "keychain";
|
|
33
34
|
}
|
|
34
|
-
if (
|
|
35
|
+
if (backend === undefined || backend === "file") {
|
|
35
36
|
return "file";
|
|
36
37
|
}
|
|
37
|
-
throw new Error(`Unsupported auth store backend: ${
|
|
38
|
+
throw new Error(`Unsupported auth store backend: ${backend}`);
|
|
38
39
|
}
|
|
39
40
|
function getOwnEnvValue(env, key) {
|
|
40
41
|
return env !== undefined && Object.prototype.hasOwnProperty.call(env, key)
|
|
@@ -24,7 +24,10 @@ export class EncryptedFileStore {
|
|
|
24
24
|
if (input.filePath === undefined) {
|
|
25
25
|
const homeDirectory = (input.getHomeDirectory ?? homedir)();
|
|
26
26
|
const defaultDirectory = input.defaultDirectory ?? ".auth-store";
|
|
27
|
-
|
|
27
|
+
const defaultFileName = input.defaultFileName ?? "credentials.enc";
|
|
28
|
+
assertSafeDefaultDirectory(defaultDirectory);
|
|
29
|
+
assertSafeDefaultFileName(defaultFileName);
|
|
30
|
+
this.filePath = path.join(homeDirectory, defaultDirectory, defaultFileName);
|
|
28
31
|
this.symbolicLinkCheckStartPath = resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory);
|
|
29
32
|
}
|
|
30
33
|
else {
|
|
@@ -154,7 +157,7 @@ function resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory) {
|
|
|
154
157
|
}
|
|
155
158
|
function getProtectedCredentialPaths(resolvedPath, symbolicLinkCheckStartPath) {
|
|
156
159
|
if (symbolicLinkCheckStartPath === null) {
|
|
157
|
-
return
|
|
160
|
+
return getExplicitProtectedCredentialPaths(resolvedPath);
|
|
158
161
|
}
|
|
159
162
|
const resolvedStartPath = path.resolve(symbolicLinkCheckStartPath);
|
|
160
163
|
if (!isPathInsideOrEqual(resolvedPath, resolvedStartPath)) {
|
|
@@ -168,6 +171,50 @@ function getProtectedCredentialPaths(resolvedPath, symbolicLinkCheckStartPath) {
|
|
|
168
171
|
}
|
|
169
172
|
return protectedPaths;
|
|
170
173
|
}
|
|
174
|
+
function assertSafeDefaultDirectory(defaultDirectory) {
|
|
175
|
+
if (path.isAbsolute(defaultDirectory) || path.win32.isAbsolute(defaultDirectory)) {
|
|
176
|
+
throw new Error("defaultDirectory must be a relative path inside the home directory");
|
|
177
|
+
}
|
|
178
|
+
for (const segment of splitPathSegments(defaultDirectory)) {
|
|
179
|
+
if (segment === "..") {
|
|
180
|
+
throw new Error("defaultDirectory must be a relative path inside the home directory");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function assertSafeDefaultFileName(defaultFileName) {
|
|
185
|
+
if (defaultFileName.trim().length === 0 ||
|
|
186
|
+
defaultFileName === "." ||
|
|
187
|
+
defaultFileName === ".." ||
|
|
188
|
+
splitPathSegments(defaultFileName).length !== 1) {
|
|
189
|
+
throw new Error("defaultFileName must be a file name without path separators");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function splitPathSegments(value) {
|
|
193
|
+
return value
|
|
194
|
+
.split("/")
|
|
195
|
+
.flatMap((segment) => segment.split("\\"))
|
|
196
|
+
.filter((segment) => segment.length > 0);
|
|
197
|
+
}
|
|
198
|
+
function getExplicitProtectedCredentialPaths(resolvedPath) {
|
|
199
|
+
const parsed = path.parse(resolvedPath);
|
|
200
|
+
const segments = resolvedPath
|
|
201
|
+
.slice(parsed.root.length)
|
|
202
|
+
.split(path.sep)
|
|
203
|
+
.filter((segment) => segment.length > 0);
|
|
204
|
+
if (segments.length <= 1) {
|
|
205
|
+
return [resolvedPath];
|
|
206
|
+
}
|
|
207
|
+
const protectedPaths = [];
|
|
208
|
+
let currentPath = parsed.root;
|
|
209
|
+
for (const [index, segment] of segments.entries()) {
|
|
210
|
+
currentPath = path.join(currentPath, segment);
|
|
211
|
+
if (index === 0) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
protectedPaths.push(currentPath);
|
|
215
|
+
}
|
|
216
|
+
return protectedPaths;
|
|
217
|
+
}
|
|
171
218
|
function isPathInsideOrEqual(childPath, parentPath) {
|
|
172
219
|
const relativePath = path.relative(parentPath, childPath);
|
|
173
220
|
return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
|
|
@@ -7,8 +7,14 @@ export class KeychainStore {
|
|
|
7
7
|
account;
|
|
8
8
|
constructor(input) {
|
|
9
9
|
this.runCommand = input.runCommand ?? runSecurityCommand;
|
|
10
|
-
this.service = input.service;
|
|
11
|
-
this.account = input.account;
|
|
10
|
+
this.service = input.service.trim();
|
|
11
|
+
this.account = input.account.trim();
|
|
12
|
+
if (this.service.length === 0) {
|
|
13
|
+
throw new Error("Keychain service must not be empty");
|
|
14
|
+
}
|
|
15
|
+
if (this.account.length === 0) {
|
|
16
|
+
throw new Error("Keychain account must not be empty");
|
|
17
|
+
}
|
|
12
18
|
}
|
|
13
19
|
async get() {
|
|
14
20
|
const result = await this.executeSecurityCommand(["find-generic-password", "-s", this.service, "-a", this.account, "-w"], "read secret from macOS Keychain");
|
|
@@ -31,8 +37,9 @@ export class KeychainStore {
|
|
|
31
37
|
"-a",
|
|
32
38
|
this.account,
|
|
33
39
|
"-U",
|
|
34
|
-
"-w"
|
|
35
|
-
|
|
40
|
+
"-w",
|
|
41
|
+
value
|
|
42
|
+
], "store secret in macOS Keychain");
|
|
36
43
|
if (getCommandExitCode(result) !== 0) {
|
|
37
44
|
throw createSecurityCliFailure("store secret in macOS Keychain", result);
|
|
38
45
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft-openapi",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.51",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"toolcraft-openapi-generate": "dist/bin/generate.js"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"toolcraft": "0.0.
|
|
31
|
+
"toolcraft": "0.0.51",
|
|
32
32
|
"auth-store": "^0.0.1",
|
|
33
33
|
"yaml": "^2.8.2"
|
|
34
34
|
},
|