toolcraft-openapi 0.0.22 → 0.0.24

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.
Files changed (63) hide show
  1. package/README.md +2 -3
  2. package/dist/auth/bearer-token-auth.js +12 -3
  3. package/dist/auth/types.d.ts +1 -1
  4. package/dist/bin/generate.d.ts +1 -1
  5. package/dist/bin/generate.js +46 -21
  6. package/dist/generate.js +6 -2
  7. package/dist/http.js +29 -17
  8. package/dist/interpreter.js +12 -3
  9. package/dist/mock/fetch.js +22 -5
  10. package/dist/network-error.js +5 -3
  11. package/dist/redaction.d.ts +3 -0
  12. package/dist/redaction.js +38 -0
  13. package/node_modules/@poe-code/design-system/dist/acp/components.js +3 -1
  14. package/node_modules/@poe-code/design-system/dist/components/browser.d.ts +1 -1
  15. package/node_modules/@poe-code/design-system/dist/components/browser.js +6 -1
  16. package/node_modules/@poe-code/design-system/dist/components/color.js +9 -8
  17. package/node_modules/@poe-code/design-system/dist/components/command-errors.js +3 -2
  18. package/node_modules/@poe-code/design-system/dist/components/detail-card.d.ts +22 -0
  19. package/node_modules/@poe-code/design-system/dist/components/detail-card.js +69 -0
  20. package/node_modules/@poe-code/design-system/dist/components/help-formatter.js +88 -11
  21. package/node_modules/@poe-code/design-system/dist/components/index.d.ts +1 -1
  22. package/node_modules/@poe-code/design-system/dist/components/index.js +1 -1
  23. package/node_modules/@poe-code/design-system/dist/components/table.d.ts +2 -0
  24. package/node_modules/@poe-code/design-system/dist/components/table.js +82 -5
  25. package/node_modules/@poe-code/design-system/dist/components/template.d.ts +4 -0
  26. package/node_modules/@poe-code/design-system/dist/components/template.js +198 -32
  27. package/node_modules/@poe-code/design-system/dist/components/text.js +29 -5
  28. package/node_modules/@poe-code/design-system/dist/dashboard/ansi.d.ts +2 -2
  29. package/node_modules/@poe-code/design-system/dist/dashboard/ansi.js +77 -32
  30. package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +28 -5
  31. package/node_modules/@poe-code/design-system/dist/dashboard/components/output-pane.js +45 -28
  32. package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.d.ts +4 -0
  33. package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.js +71 -0
  34. package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
  35. package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +6 -0
  36. package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +32 -10
  37. package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +3 -0
  38. package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +57 -6
  39. package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +1 -0
  40. package/node_modules/@poe-code/design-system/dist/explorer/state.js +12 -15
  41. package/node_modules/@poe-code/design-system/dist/index.d.ts +3 -1
  42. package/node_modules/@poe-code/design-system/dist/index.js +2 -1
  43. package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -1
  44. package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +8 -5
  45. package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +1 -1
  46. package/node_modules/@poe-code/design-system/dist/static/menu.js +8 -2
  47. package/node_modules/@poe-code/design-system/dist/static/spinner.js +10 -4
  48. package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/frontmatter.js +9 -2
  49. package/node_modules/@poe-code/design-system/dist/terminal-markdown/renderer.js +19 -2
  50. package/node_modules/@poe-code/design-system/package.json +2 -1
  51. package/node_modules/auth-store/dist/create-secret-store.js +4 -1
  52. package/node_modules/auth-store/dist/encrypted-file-store.d.ts +7 -0
  53. package/node_modules/auth-store/dist/encrypted-file-store.js +69 -7
  54. package/node_modules/auth-store/dist/index.d.ts +1 -1
  55. package/node_modules/auth-store/dist/keychain-store.d.ts +4 -1
  56. package/node_modules/auth-store/dist/keychain-store.js +18 -16
  57. package/node_modules/auth-store/dist/provider-store.d.ts +5 -1
  58. package/node_modules/auth-store/dist/provider-store.js +55 -7
  59. package/node_modules/auth-store/dist/types.d.ts +3 -1
  60. package/node_modules/auth-store/package.json +2 -1
  61. package/package.json +3 -3
  62. package/dist/lock.d.ts +0 -14
  63. package/dist/lock.js +0 -152
@@ -2,13 +2,19 @@ import { color } from "../components/color.js";
2
2
  import { symbols } from "../components/symbols.js";
3
3
  import { resolveOutputFormat } from "../internal/output-format.js";
4
4
  import { getTheme } from "../internal/theme-detect.js";
5
+ function renderMarkdownInline(value) {
6
+ return value.replaceAll("\r\n", " ").replaceAll("\n", " ").replaceAll("\r", " ");
7
+ }
5
8
  export function renderMenu(opts) {
6
9
  const format = resolveOutputFormat();
7
10
  const selectedIndex = opts.selectedIndex ?? 0;
11
+ if (!Number.isInteger(selectedIndex) || !Number.isFinite(selectedIndex)) {
12
+ throw new Error("selectedIndex must be a finite integer.");
13
+ }
8
14
  if (format === "markdown") {
9
15
  return [
10
- `**${opts.message}**`,
11
- ...opts.options.map((option, index) => `- [${index === selectedIndex ? "x" : " "}] ${option.label}`)
16
+ `**${renderMarkdownInline(opts.message)}**`,
17
+ ...opts.options.map((option, index) => `- [${index === selectedIndex ? "x" : " "}] ${renderMarkdownInline(option.label)}`)
12
18
  ].join("\n");
13
19
  }
14
20
  if (format === "json") {
@@ -1,7 +1,7 @@
1
1
  import { color } from "../components/color.js";
2
2
  import { symbols } from "../components/symbols.js";
3
3
  import { resolveOutputFormat } from "../internal/output-format.js";
4
- export const SPINNER_FRAMES = ["◒", "◐", "◓", "◑"];
4
+ export const SPINNER_FRAMES = Object.freeze(["◒", "◐", "◓", "◑"]);
5
5
  export function renderSpinnerFrame(options) {
6
6
  const format = resolveOutputFormat();
7
7
  if (format === "markdown") {
@@ -16,22 +16,28 @@ export function renderSpinnerFrame(options) {
16
16
  })}\n`;
17
17
  }
18
18
  const frame = options.frame ?? 0;
19
- const spinnerChar = color.magenta(SPINNER_FRAMES[frame % SPINNER_FRAMES.length]);
19
+ const index = ((frame % SPINNER_FRAMES.length) + SPINNER_FRAMES.length) % SPINNER_FRAMES.length;
20
+ const spinnerChar = color.magenta(SPINNER_FRAMES[index]);
20
21
  const timerSuffix = options.timer ? color.dim(` [${options.timer}]`) : "";
21
22
  const bar = color.gray(symbols.bar);
22
23
  return `${spinnerChar} ${options.message}${timerSuffix}\n${bar}`;
23
24
  }
25
+ function renderMarkdownInline(value) {
26
+ return value.replaceAll("\r\n", " ").replaceAll("\n", " ").replaceAll("\r", " ");
27
+ }
24
28
  export function renderSpinnerStopped(options) {
25
29
  const format = resolveOutputFormat();
26
30
  if (format === "markdown") {
27
- return `- ${options.message}${options.timer ? ` [${options.timer}]` : ""}\n`;
31
+ return `- ${renderMarkdownInline(options.message)}${options.timer ? ` [${renderMarkdownInline(options.timer)}]` : ""}\n`;
28
32
  }
29
33
  if (format === "json") {
30
34
  return `${JSON.stringify({
31
35
  type: "spinner",
32
36
  state: "stopped",
33
37
  message: options.message,
34
- ...(options.timer ? { timer: options.timer } : {})
38
+ code: options.code ?? 0,
39
+ ...(options.timer ? { timer: options.timer } : {}),
40
+ ...(options.subtext ? { subtext: options.subtext } : {})
35
41
  })}\n`;
36
42
  }
37
43
  const code = options.code ?? 0;
@@ -32,7 +32,12 @@ class YamlSubsetParser {
32
32
  throw new FrontmatterParseError("Invalid mapping entry.");
33
33
  }
34
34
  this.position += 1;
35
- result[entry.key] = this.readEntryValue(entry, expectedIndent);
35
+ Object.defineProperty(result, entry.key, {
36
+ configurable: true,
37
+ enumerable: true,
38
+ value: this.readEntryValue(entry, expectedIndent),
39
+ writable: true
40
+ });
36
41
  }
37
42
  return result;
38
43
  }
@@ -345,8 +350,10 @@ function sliceFrontmatterBlock(content, start, end) {
345
350
  function startsWithFrontmatterFence(value) {
346
351
  return (value.startsWith("---\n") ||
347
352
  value.startsWith("---\r\n") ||
353
+ value.startsWith("---\r") ||
348
354
  value.startsWith("\uFEFF---\n") ||
349
- value.startsWith("\uFEFF---\r\n"));
355
+ value.startsWith("\uFEFF---\r\n") ||
356
+ value.startsWith("\uFEFF---\r"));
350
357
  }
351
358
  function stripBom(value) {
352
359
  return value.startsWith("\uFEFF") ? value.slice(1) : value;
@@ -6,7 +6,11 @@ import { typography } from "../tokens/typography.js";
6
6
  import { widths } from "../tokens/widths.js";
7
7
  const lineChar = "─";
8
8
  export function render(ast, options = {}) {
9
- const width = Math.max(1, options.width ?? process.stdout.columns ?? widths.maxLine);
9
+ const requestedWidth = options.width ?? process.stdout.columns ?? widths.maxLine;
10
+ if (!Number.isFinite(requestedWidth) || requestedWidth <= 0) {
11
+ throw new Error("width must be a positive finite number.");
12
+ }
13
+ const width = Math.max(1, requestedWidth);
10
14
  const context = {
11
15
  width,
12
16
  showFrontmatter: options.showFrontmatter ?? false,
@@ -215,7 +219,20 @@ function formatFrontmatterValue(value) {
215
219
  if (typeof value === "number" || typeof value === "boolean") {
216
220
  return String(value);
217
221
  }
218
- return JSON.stringify(value);
222
+ const ancestors = [];
223
+ return JSON.stringify(value, function (_key, nestedValue) {
224
+ if (typeof nestedValue !== "object" || nestedValue === null) {
225
+ return nestedValue;
226
+ }
227
+ while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
228
+ ancestors.pop();
229
+ }
230
+ if (ancestors.includes(nestedValue)) {
231
+ return "[Circular]";
232
+ }
233
+ ancestors.push(nestedValue);
234
+ return nestedValue;
235
+ });
219
236
  }
220
237
  function renderHtml(node, context) {
221
238
  const value = stripHtmlTags(node.value).trim();
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@poe-code/design-system",
3
3
  "version": "0.0.2",
4
+ "private": true,
4
5
  "type": "module",
5
6
  "main": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
7
8
  "scripts": {
8
- "build": "tsc",
9
+ "build": "node ../../scripts/guard-package-dist.mjs && tsc",
9
10
  "postbuild": "node scripts/smoke-built-exports.cjs",
10
11
  "lint": "cd ../.. && eslint packages/design-system --ext ts && tsc -p packages/design-system/tsconfig.json --noEmit",
11
12
  "test": "cd ../.. && vitest run $(rg --files packages/design-system/src -g '*.test.ts' | sort | tr '\\n' ' ')",
@@ -31,5 +31,8 @@ function resolveBackend(input) {
31
31
  if (configuredBackend === "keychain") {
32
32
  return "keychain";
33
33
  }
34
- return "file";
34
+ if (configuredBackend === undefined || configuredBackend === "file") {
35
+ return "file";
36
+ }
37
+ throw new Error(`Unsupported auth store backend: ${configuredBackend}`);
35
38
  }
@@ -7,10 +7,16 @@ export interface EncryptedFileStoreFileSystem {
7
7
  readFile(path: string, encoding: BufferEncoding): Promise<string>;
8
8
  writeFile(path: string, data: string | NodeJS.ArrayBufferView, options?: {
9
9
  encoding?: BufferEncoding;
10
+ flag?: string;
11
+ mode?: number;
10
12
  }): Promise<void>;
11
13
  mkdir(path: string, options?: {
12
14
  recursive?: boolean;
13
15
  }): Promise<void | string | undefined>;
16
+ rename(oldPath: string, newPath: string): Promise<void>;
17
+ lstat(path: string): Promise<{
18
+ isSymbolicLink(): boolean;
19
+ }>;
14
20
  unlink(path: string): Promise<void>;
15
21
  chmod(path: string, mode: number): Promise<void>;
16
22
  }
@@ -35,5 +41,6 @@ export declare class EncryptedFileStore implements SecretStore {
35
41
  get(): Promise<string | null>;
36
42
  set(value: string): Promise<void>;
37
43
  delete(): Promise<void>;
44
+ private assertRegularCredentialPath;
38
45
  private getEncryptionKey;
39
46
  }
@@ -9,6 +9,7 @@ const ENCRYPTION_KEY_BYTES = 32;
9
9
  const ENCRYPTION_IV_BYTES = 12;
10
10
  const ENCRYPTION_AUTH_TAG_BYTES = 16;
11
11
  const ENCRYPTION_FILE_MODE = 0o600;
12
+ let temporaryFileSequence = 0;
12
13
  export class EncryptedFileStore {
13
14
  fs;
14
15
  filePath;
@@ -24,6 +25,7 @@ export class EncryptedFileStore {
24
25
  this.getRandomBytes = input.getRandomBytes ?? randomBytes;
25
26
  }
26
27
  async get() {
28
+ await this.assertRegularCredentialPath();
27
29
  let rawDocument;
28
30
  try {
29
31
  rawDocument = await this.fs.readFile(this.filePath, "utf8");
@@ -57,6 +59,7 @@ export class EncryptedFileStore {
57
59
  }
58
60
  }
59
61
  async set(value) {
62
+ await this.assertRegularCredentialPath();
60
63
  const key = await this.getEncryptionKey();
61
64
  const iv = this.getRandomBytes(ENCRYPTION_IV_BYTES);
62
65
  const cipher = createCipheriv(ENCRYPTION_ALGORITHM, key, iv);
@@ -72,12 +75,26 @@ export class EncryptedFileStore {
72
75
  ciphertext: ciphertext.toString("base64")
73
76
  };
74
77
  await this.fs.mkdir(path.dirname(this.filePath), { recursive: true });
75
- await this.fs.writeFile(this.filePath, JSON.stringify(document), {
76
- encoding: "utf8"
77
- });
78
- await this.fs.chmod(this.filePath, ENCRYPTION_FILE_MODE);
78
+ await this.assertRegularCredentialPath();
79
+ const temporaryPath = `${this.filePath}.${process.pid}.${temporaryFileSequence++}.tmp`;
80
+ try {
81
+ await this.fs.writeFile(temporaryPath, JSON.stringify(document), {
82
+ encoding: "utf8",
83
+ flag: "wx",
84
+ mode: ENCRYPTION_FILE_MODE
85
+ });
86
+ await this.fs.chmod(temporaryPath, ENCRYPTION_FILE_MODE);
87
+ await this.fs.rename(temporaryPath, this.filePath);
88
+ }
89
+ catch (error) {
90
+ if (!isAlreadyExistsError(error)) {
91
+ await removeIfPresent(this.fs, temporaryPath).catch(() => undefined);
92
+ }
93
+ throw error;
94
+ }
79
95
  }
80
96
  async delete() {
97
+ await this.assertRegularCredentialPath();
81
98
  try {
82
99
  await this.fs.unlink(this.filePath);
83
100
  }
@@ -87,13 +104,53 @@ export class EncryptedFileStore {
87
104
  }
88
105
  }
89
106
  }
107
+ async assertRegularCredentialPath() {
108
+ const resolvedPath = path.resolve(this.filePath);
109
+ const protectedPaths = [path.dirname(resolvedPath), resolvedPath];
110
+ for (const currentPath of protectedPaths) {
111
+ try {
112
+ const stats = await this.fs.lstat(currentPath);
113
+ if (stats.isSymbolicLink()) {
114
+ throw new Error(`Refusing to use encrypted credential path through symbolic link: ${currentPath}`);
115
+ }
116
+ }
117
+ catch (error) {
118
+ if (isNotFoundError(error)) {
119
+ return;
120
+ }
121
+ throw error;
122
+ }
123
+ }
124
+ }
90
125
  getEncryptionKey() {
91
126
  if (!this.keyPromise) {
92
- this.keyPromise = deriveEncryptionKey(this.getMachineIdentity, this.salt);
127
+ const retryableKeyPromise = deriveEncryptionKey(this.getMachineIdentity, this.salt).catch((error) => {
128
+ if (this.keyPromise === retryableKeyPromise) {
129
+ this.keyPromise = null;
130
+ }
131
+ throw error;
132
+ });
133
+ this.keyPromise = retryableKeyPromise;
93
134
  }
94
135
  return this.keyPromise;
95
136
  }
96
137
  }
138
+ async function removeIfPresent(fileSystem, filePath) {
139
+ try {
140
+ await fileSystem.unlink(filePath);
141
+ }
142
+ catch (error) {
143
+ if (!isNotFoundError(error)) {
144
+ throw error;
145
+ }
146
+ }
147
+ }
148
+ function isAlreadyExistsError(error) {
149
+ return (typeof error === "object" &&
150
+ error !== null &&
151
+ "code" in error &&
152
+ error.code === "EEXIST");
153
+ }
97
154
  function defaultMachineIdentity() {
98
155
  return {
99
156
  hostname: hostname(),
@@ -103,7 +160,7 @@ function defaultMachineIdentity() {
103
160
  async function deriveEncryptionKey(getMachineIdentity, salt) {
104
161
  const machineIdentity = await getMachineIdentity();
105
162
  const secret = `${machineIdentity.hostname}:${machineIdentity.username}`;
106
- const cacheKey = `${secret}:${salt}`;
163
+ const cacheKey = JSON.stringify([machineIdentity.hostname, machineIdentity.username, salt]);
107
164
  const cached = derivedKeyCache.get(cacheKey);
108
165
  if (cached) {
109
166
  return cached;
@@ -118,7 +175,12 @@ async function deriveEncryptionKey(getMachineIdentity, salt) {
118
175
  });
119
176
  });
120
177
  derivedKeyCache.set(cacheKey, keyPromise);
121
- return keyPromise;
178
+ return keyPromise.catch((error) => {
179
+ if (derivedKeyCache.get(cacheKey) === keyPromise) {
180
+ derivedKeyCache.delete(cacheKey);
181
+ }
182
+ throw error;
183
+ });
122
184
  }
123
185
  function parseEncryptedDocument(raw) {
124
186
  try {
@@ -4,4 +4,4 @@ export { KeychainStore } from "./keychain-store.js";
4
4
  export { key, MigratingSecretStore } from "./provider-store.js";
5
5
  export type { SecretStore, StoreBackend, CreateSecretStoreInput, CreateSecretStoreResult } from "./types.js";
6
6
  export type { MachineIdentity, EncryptedFileStoreInput, EncryptedFileStoreFileSystem } from "./encrypted-file-store.js";
7
- export type { KeychainStoreInput, KeychainCommandRunner, KeychainCommandResult } from "./keychain-store.js";
7
+ export type { KeychainStoreInput, KeychainCommandRunner, KeychainCommandResult, KeychainCommandOptions } from "./keychain-store.js";
@@ -4,7 +4,10 @@ export interface KeychainCommandResult {
4
4
  stderr: string;
5
5
  exitCode: number;
6
6
  }
7
- export type KeychainCommandRunner = (command: string, args: string[]) => Promise<KeychainCommandResult>;
7
+ export interface KeychainCommandOptions {
8
+ stdin?: string;
9
+ }
10
+ export type KeychainCommandRunner = (command: string, args: string[], options?: KeychainCommandOptions) => Promise<KeychainCommandResult>;
8
11
  export interface KeychainStoreInput {
9
12
  runCommand?: KeychainCommandRunner;
10
13
  service: string;
@@ -21,16 +21,18 @@ export class KeychainStore {
21
21
  throw createSecurityCliFailure("read secret from macOS Keychain", result);
22
22
  }
23
23
  async set(value) {
24
+ if (value.includes("\n") || value.includes("\r")) {
25
+ throw new Error("Keychain secrets cannot contain line breaks");
26
+ }
24
27
  const result = await this.executeSecurityCommand([
25
28
  "add-generic-password",
26
29
  "-s",
27
30
  this.service,
28
31
  "-a",
29
32
  this.account,
30
- "-w",
31
- value,
32
- "-U"
33
- ], "store secret in macOS Keychain");
33
+ "-U",
34
+ "-w"
35
+ ], "store secret in macOS Keychain", { stdin: value });
34
36
  if (result.exitCode !== 0) {
35
37
  throw createSecurityCliFailure("store secret in macOS Keychain", result);
36
38
  }
@@ -42,9 +44,12 @@ export class KeychainStore {
42
44
  }
43
45
  throw createSecurityCliFailure("delete secret from macOS Keychain", result);
44
46
  }
45
- async executeSecurityCommand(args, operation) {
47
+ async executeSecurityCommand(args, operation, options) {
46
48
  try {
47
- return await this.runCommand(SECURITY_CLI, args);
49
+ if (options === undefined) {
50
+ return await this.runCommand(SECURITY_CLI, args);
51
+ }
52
+ return await this.runCommand(SECURITY_CLI, args, options);
48
53
  }
49
54
  catch (error) {
50
55
  const message = error instanceof Error ? error.message : String(error);
@@ -52,10 +57,10 @@ export class KeychainStore {
52
57
  }
53
58
  }
54
59
  }
55
- function runSecurityCommand(command, args) {
60
+ function runSecurityCommand(command, args, options) {
56
61
  return new Promise((resolve) => {
57
62
  const child = spawn(command, args, {
58
- stdio: ["ignore", "pipe", "pipe"]
63
+ stdio: [options?.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
59
64
  });
60
65
  let stdout = "";
61
66
  let stderr = "";
@@ -67,6 +72,9 @@ function runSecurityCommand(command, args) {
67
72
  child.stderr?.on("data", (chunk) => {
68
73
  stderr += chunk.toString();
69
74
  });
75
+ if (options?.stdin !== undefined) {
76
+ child.stdin?.end(options.stdin);
77
+ }
70
78
  child.on("error", (error) => {
71
79
  const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
72
80
  resolve({
@@ -79,7 +87,7 @@ function runSecurityCommand(command, args) {
79
87
  resolve({
80
88
  stdout,
81
89
  stderr,
82
- exitCode: code ?? 0
90
+ exitCode: code ?? 1
83
91
  });
84
92
  });
85
93
  });
@@ -94,13 +102,7 @@ function stripTrailingLineBreak(value) {
94
102
  return value;
95
103
  }
96
104
  function isKeychainEntryNotFound(result) {
97
- if (result.exitCode === KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE) {
98
- return true;
99
- }
100
- const output = `${result.stderr}\n${result.stdout}`.toLowerCase();
101
- return (output.includes("could not be found") ||
102
- output.includes("item not found") ||
103
- output.includes("errsecitemnotfound"));
105
+ return result.exitCode === KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE;
104
106
  }
105
107
  function createSecurityCliFailure(operation, result) {
106
108
  const details = result.stderr.trim() || result.stdout.trim();
@@ -3,8 +3,12 @@ export declare function key(providerId: string): string;
3
3
  export declare class MigratingSecretStore implements SecretStore {
4
4
  private readonly store;
5
5
  private readonly legacyStore;
6
+ private pendingMutation;
6
7
  constructor(store: SecretStore, legacyStore?: SecretStore | null);
7
- get(): Promise<string | null>;
8
+ get(options?: {
9
+ readOnly?: boolean;
10
+ }): Promise<string | null>;
8
11
  set(value: string): Promise<void>;
9
12
  delete(): Promise<void>;
13
+ private mutate;
10
14
  }
@@ -4,27 +4,75 @@ export function key(providerId) {
4
4
  export class MigratingSecretStore {
5
5
  store;
6
6
  legacyStore;
7
+ pendingMutation = Promise.resolve();
7
8
  constructor(store, legacyStore = null) {
8
9
  this.store = store;
9
10
  this.legacyStore = legacyStore;
10
11
  }
11
- async get() {
12
+ async get(options = {}) {
12
13
  const value = await this.store.get();
13
14
  if (value !== null || !this.legacyStore) {
14
15
  return value;
15
16
  }
16
17
  const legacyValue = await this.legacyStore.get();
17
- if (legacyValue !== null) {
18
- await this.store.set(legacyValue);
18
+ if (legacyValue !== null && !options.readOnly) {
19
+ await this.mutate(async () => {
20
+ if (await this.store.get() === null) {
21
+ try {
22
+ await this.store.set(legacyValue);
23
+ }
24
+ catch {
25
+ return;
26
+ }
27
+ }
28
+ });
19
29
  }
20
30
  return legacyValue;
21
31
  }
22
32
  async set(value) {
23
- await this.store.set(value);
24
- await this.legacyStore?.set(value);
33
+ await this.mutate(async () => {
34
+ const previousValue = await this.store.get();
35
+ const previousLegacyValue = await this.legacyStore?.get() ?? null;
36
+ await this.store.set(value);
37
+ try {
38
+ await this.legacyStore?.set(value);
39
+ }
40
+ catch (error) {
41
+ await restore(this.store, previousValue);
42
+ if (this.legacyStore) {
43
+ await restore(this.legacyStore, previousLegacyValue);
44
+ }
45
+ throw error;
46
+ }
47
+ });
25
48
  }
26
49
  async delete() {
27
- await this.store.delete();
28
- await this.legacyStore?.delete();
50
+ await this.mutate(async () => {
51
+ const previousValue = await this.store.get();
52
+ const previousLegacyValue = await this.legacyStore?.get() ?? null;
53
+ await this.store.delete();
54
+ try {
55
+ await this.legacyStore?.delete();
56
+ }
57
+ catch (error) {
58
+ await restore(this.store, previousValue);
59
+ if (this.legacyStore) {
60
+ await restore(this.legacyStore, previousLegacyValue);
61
+ }
62
+ throw error;
63
+ }
64
+ });
29
65
  }
66
+ async mutate(action) {
67
+ const operation = this.pendingMutation.then(action, action);
68
+ this.pendingMutation = operation.catch(() => undefined);
69
+ await operation;
70
+ }
71
+ }
72
+ async function restore(store, value) {
73
+ if (value === null) {
74
+ await store.delete();
75
+ return;
76
+ }
77
+ await store.set(value);
30
78
  }
@@ -1,7 +1,9 @@
1
1
  import type { EncryptedFileStoreInput } from "./encrypted-file-store.js";
2
2
  import type { KeychainStoreInput } from "./keychain-store.js";
3
3
  export interface SecretStore {
4
- get(): Promise<string | null>;
4
+ get(options?: {
5
+ readOnly?: boolean;
6
+ }): Promise<string | null>;
5
7
  set(value: string): Promise<void>;
6
8
  delete(): Promise<void>;
7
9
  }
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "auth-store",
3
3
  "version": "0.0.1",
4
+ "private": true,
4
5
  "type": "module",
5
6
  "main": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
@@ -11,7 +12,7 @@
11
12
  }
12
13
  },
13
14
  "scripts": {
14
- "build": "tsc"
15
+ "build": "node ../../scripts/guard-package-dist.mjs && tsc"
15
16
  },
16
17
  "dependencies": {},
17
18
  "files": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-openapi",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,7 +18,7 @@
18
18
  "build": "rm -rf dist && tsc",
19
19
  "test": "cd ../.. && vitest run packages/toolcraft-openapi/src",
20
20
  "test:unit": "cd ../.. && vitest run packages/toolcraft-openapi/src",
21
- "prepack": "node ../../scripts/manage-bundled-workspace-deps.mjs prepare . @poe-code/design-system auth-store",
21
+ "prepack": "node ../../scripts/set-bin-executable.mjs && node ../../scripts/manage-bundled-workspace-deps.mjs prepare . @poe-code/design-system auth-store",
22
22
  "postpack": "node ../../scripts/manage-bundled-workspace-deps.mjs cleanup . @poe-code/design-system auth-store"
23
23
  },
24
24
  "files": [
@@ -31,8 +31,8 @@
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.24",
34
35
  "auth-store": "^0.0.1",
35
- "toolcraft": "^0.0.22",
36
36
  "yaml": "^2.8.2"
37
37
  },
38
38
  "engines": {
package/dist/lock.d.ts DELETED
@@ -1,14 +0,0 @@
1
- export interface OpenApiLock {
2
- specSha: string;
3
- }
4
- export interface LockFileSystem {
5
- mkdir(directoryPath: string, options?: {
6
- recursive?: boolean;
7
- }): Promise<unknown>;
8
- readFile(filePath: string, encoding: BufferEncoding): Promise<string>;
9
- writeFile(filePath: string, contents: string, encoding: BufferEncoding): Promise<void>;
10
- }
11
- export declare function parseOpenApiLock(contents: string, lockPath: string): OpenApiLock | null;
12
- export declare function stringifyOpenApiLock(lock: OpenApiLock): string;
13
- export declare function readOpenApiLock(fs: Pick<LockFileSystem, "readFile">, lockPath: string): Promise<OpenApiLock | null>;
14
- export declare function writeOpenApiLock(fs: LockFileSystem, lockPath: string, lock: OpenApiLock): Promise<void>;