toolcraft-openapi 0.0.24 → 0.0.25
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/bin/generate.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
interface GenerateCliFileSystem {
|
|
3
|
+
lstat(targetPath: string): Promise<{
|
|
4
|
+
isDirectory(): boolean;
|
|
5
|
+
isSymbolicLink(): boolean;
|
|
6
|
+
}>;
|
|
3
7
|
mkdir(directoryPath: string, options?: {
|
|
4
8
|
recursive?: boolean;
|
|
5
9
|
}): Promise<unknown>;
|
|
@@ -9,9 +13,6 @@ interface GenerateCliFileSystem {
|
|
|
9
13
|
force?: boolean;
|
|
10
14
|
}): Promise<void>;
|
|
11
15
|
realpath(targetPath: string): Promise<string>;
|
|
12
|
-
stat(targetPath: string): Promise<{
|
|
13
|
-
isDirectory(): boolean;
|
|
14
|
-
}>;
|
|
15
16
|
writeFile(filePath: string, contents: string, encoding: BufferEncoding): Promise<void>;
|
|
16
17
|
}
|
|
17
18
|
interface GenerateCliWriter {
|
package/dist/bin/generate.js
CHANGED
|
@@ -81,7 +81,7 @@ export async function syncGeneratedClient(options, services) {
|
|
|
81
81
|
if (!options.check && drifted) {
|
|
82
82
|
try {
|
|
83
83
|
await writeGeneratedFiles(services.fs, outputDir, updatedFiles);
|
|
84
|
-
await deleteGeneratedFiles(services.fs, deletedFiles);
|
|
84
|
+
await deleteGeneratedFiles(services.fs, outputDir, deletedFiles);
|
|
85
85
|
}
|
|
86
86
|
catch (error) {
|
|
87
87
|
await restoreGeneratedFiles(services.fs, outputDir, currentFiles, updatedFiles, deletedFiles);
|
|
@@ -170,10 +170,17 @@ function tryParseUrl(input) {
|
|
|
170
170
|
async function readGeneratedFiles(fs, directoryPath) {
|
|
171
171
|
const files = new Map();
|
|
172
172
|
try {
|
|
173
|
+
const directoryStats = await fs.lstat(directoryPath);
|
|
174
|
+
if (directoryStats.isSymbolicLink()) {
|
|
175
|
+
throw new Error("Generated output must remain inside the output directory.");
|
|
176
|
+
}
|
|
173
177
|
const entries = await fs.readdir(directoryPath);
|
|
174
178
|
for (const entry of entries) {
|
|
175
179
|
const entryPath = path.resolve(directoryPath, entry);
|
|
176
|
-
const stats = await fs.
|
|
180
|
+
const stats = await fs.lstat(entryPath);
|
|
181
|
+
if (stats.isSymbolicLink()) {
|
|
182
|
+
throw new Error("Generated output must remain inside the output directory.");
|
|
183
|
+
}
|
|
177
184
|
if (stats.isDirectory()) {
|
|
178
185
|
for (const [nestedPath, nestedContents] of await readGeneratedFiles(fs, entryPath)) {
|
|
179
186
|
files.set(nestedPath, nestedContents);
|
|
@@ -228,8 +235,9 @@ async function assertSafeOutputPath(fs, outputDir, filePath) {
|
|
|
228
235
|
throw new Error("Generated output must remain inside the output directory.");
|
|
229
236
|
}
|
|
230
237
|
}
|
|
231
|
-
async function deleteGeneratedFiles(fs, filePaths) {
|
|
238
|
+
async function deleteGeneratedFiles(fs, outputDir, filePaths) {
|
|
232
239
|
for (const filePath of filePaths) {
|
|
240
|
+
await assertSafeOutputPath(fs, outputDir, filePath);
|
|
233
241
|
await fs.rm(filePath, { force: true });
|
|
234
242
|
}
|
|
235
243
|
}
|
|
@@ -237,6 +245,7 @@ async function restoreGeneratedFiles(fs, outputDir, currentFiles, updatedFiles,
|
|
|
237
245
|
for (const file of updatedFiles) {
|
|
238
246
|
const previousContents = currentFiles.get(file.path);
|
|
239
247
|
if (previousContents === undefined) {
|
|
248
|
+
await assertSafeOutputPath(fs, outputDir, file.path);
|
|
240
249
|
await fs.rm(file.path, { force: true });
|
|
241
250
|
continue;
|
|
242
251
|
}
|
|
@@ -33,6 +33,7 @@ export interface EncryptedFileStoreInput {
|
|
|
33
33
|
export declare class EncryptedFileStore implements SecretStore {
|
|
34
34
|
private readonly fs;
|
|
35
35
|
private readonly filePath;
|
|
36
|
+
private readonly symbolicLinkCheckStartPath;
|
|
36
37
|
private readonly salt;
|
|
37
38
|
private readonly getMachineIdentity;
|
|
38
39
|
private readonly getRandomBytes;
|
|
@@ -13,6 +13,7 @@ let temporaryFileSequence = 0;
|
|
|
13
13
|
export class EncryptedFileStore {
|
|
14
14
|
fs;
|
|
15
15
|
filePath;
|
|
16
|
+
symbolicLinkCheckStartPath;
|
|
16
17
|
salt;
|
|
17
18
|
getMachineIdentity;
|
|
18
19
|
getRandomBytes;
|
|
@@ -20,7 +21,16 @@ export class EncryptedFileStore {
|
|
|
20
21
|
constructor(input) {
|
|
21
22
|
this.fs = input.fs ?? fs;
|
|
22
23
|
this.salt = input.salt;
|
|
23
|
-
|
|
24
|
+
if (input.filePath === undefined) {
|
|
25
|
+
const homeDirectory = (input.getHomeDirectory ?? homedir)();
|
|
26
|
+
const defaultDirectory = input.defaultDirectory ?? ".auth-store";
|
|
27
|
+
this.filePath = path.join(homeDirectory, defaultDirectory, input.defaultFileName ?? "credentials.enc");
|
|
28
|
+
this.symbolicLinkCheckStartPath = resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
this.filePath = input.filePath;
|
|
32
|
+
this.symbolicLinkCheckStartPath = null;
|
|
33
|
+
}
|
|
24
34
|
this.getMachineIdentity = input.getMachineIdentity ?? defaultMachineIdentity;
|
|
25
35
|
this.getRandomBytes = input.getRandomBytes ?? randomBytes;
|
|
26
36
|
}
|
|
@@ -106,7 +116,7 @@ export class EncryptedFileStore {
|
|
|
106
116
|
}
|
|
107
117
|
async assertRegularCredentialPath() {
|
|
108
118
|
const resolvedPath = path.resolve(this.filePath);
|
|
109
|
-
const protectedPaths =
|
|
119
|
+
const protectedPaths = getProtectedCredentialPaths(resolvedPath, this.symbolicLinkCheckStartPath);
|
|
110
120
|
for (const currentPath of protectedPaths) {
|
|
111
121
|
try {
|
|
112
122
|
const stats = await this.fs.lstat(currentPath);
|
|
@@ -135,6 +145,30 @@ export class EncryptedFileStore {
|
|
|
135
145
|
return this.keyPromise;
|
|
136
146
|
}
|
|
137
147
|
}
|
|
148
|
+
function resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory) {
|
|
149
|
+
const [firstSegment] = defaultDirectory.split(/[\\/]+/).filter(Boolean);
|
|
150
|
+
return path.resolve(homeDirectory, firstSegment ?? ".");
|
|
151
|
+
}
|
|
152
|
+
function getProtectedCredentialPaths(resolvedPath, symbolicLinkCheckStartPath) {
|
|
153
|
+
if (symbolicLinkCheckStartPath === null) {
|
|
154
|
+
return [path.dirname(resolvedPath), resolvedPath];
|
|
155
|
+
}
|
|
156
|
+
const resolvedStartPath = path.resolve(symbolicLinkCheckStartPath);
|
|
157
|
+
if (!isPathInsideOrEqual(resolvedPath, resolvedStartPath)) {
|
|
158
|
+
return [path.dirname(resolvedPath), resolvedPath];
|
|
159
|
+
}
|
|
160
|
+
const protectedPaths = [resolvedStartPath];
|
|
161
|
+
let currentPath = resolvedStartPath;
|
|
162
|
+
for (const segment of path.relative(resolvedStartPath, resolvedPath).split(path.sep).filter(Boolean)) {
|
|
163
|
+
currentPath = path.join(currentPath, segment);
|
|
164
|
+
protectedPaths.push(currentPath);
|
|
165
|
+
}
|
|
166
|
+
return protectedPaths;
|
|
167
|
+
}
|
|
168
|
+
function isPathInsideOrEqual(childPath, parentPath) {
|
|
169
|
+
const relativePath = path.relative(parentPath, childPath);
|
|
170
|
+
return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
|
|
171
|
+
}
|
|
138
172
|
async function removeIfPresent(fileSystem, filePath) {
|
|
139
173
|
try {
|
|
140
174
|
await fileSystem.unlink(filePath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft-openapi",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@clack/core": "^1.0.0",
|
|
32
32
|
"@clack/prompts": "^1.0.0",
|
|
33
33
|
"@poe-code/design-system": "^0.0.2",
|
|
34
|
-
"toolcraft": "0.0.
|
|
34
|
+
"toolcraft": "0.0.25",
|
|
35
35
|
"auth-store": "^0.0.1",
|
|
36
36
|
"yaml": "^2.8.2"
|
|
37
37
|
},
|