@metamask-previews/wallet-cli 0.0.0-preview-1132ac340 → 0.0.0-preview-56b2c8bdd

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 (87) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +27 -0
  3. package/dist/commands/daemon/call.cjs +83 -0
  4. package/dist/commands/daemon/call.cjs.map +1 -0
  5. package/dist/commands/daemon/call.d.cts +14 -0
  6. package/dist/commands/daemon/call.d.cts.map +1 -0
  7. package/dist/commands/daemon/call.d.mts +14 -0
  8. package/dist/commands/daemon/call.d.mts.map +1 -0
  9. package/dist/commands/daemon/call.mjs +81 -0
  10. package/dist/commands/daemon/call.mjs.map +1 -0
  11. package/dist/commands/daemon/purge.cjs +58 -0
  12. package/dist/commands/daemon/purge.cjs.map +1 -0
  13. package/dist/commands/daemon/purge.d.cts +10 -0
  14. package/dist/commands/daemon/purge.d.cts.map +1 -0
  15. package/dist/commands/daemon/purge.d.mts +10 -0
  16. package/dist/commands/daemon/purge.d.mts.map +1 -0
  17. package/dist/commands/daemon/purge.mjs +56 -0
  18. package/dist/commands/daemon/purge.mjs.map +1 -0
  19. package/dist/commands/daemon/start.cjs +48 -0
  20. package/dist/commands/daemon/start.cjs.map +1 -0
  21. package/dist/commands/daemon/start.d.cts +12 -0
  22. package/dist/commands/daemon/start.d.cts.map +1 -0
  23. package/dist/commands/daemon/start.d.mts +12 -0
  24. package/dist/commands/daemon/start.d.mts.map +1 -0
  25. package/dist/commands/daemon/start.mjs +46 -0
  26. package/dist/commands/daemon/start.mjs.map +1 -0
  27. package/dist/commands/daemon/status.cjs +59 -0
  28. package/dist/commands/daemon/status.cjs.map +1 -0
  29. package/dist/commands/daemon/status.d.cts +7 -0
  30. package/dist/commands/daemon/status.d.cts.map +1 -0
  31. package/dist/commands/daemon/status.d.mts +7 -0
  32. package/dist/commands/daemon/status.d.mts.map +1 -0
  33. package/dist/commands/daemon/status.mjs +57 -0
  34. package/dist/commands/daemon/status.mjs.map +1 -0
  35. package/dist/commands/daemon/stop.cjs +26 -0
  36. package/dist/commands/daemon/stop.cjs.map +1 -0
  37. package/dist/commands/daemon/stop.d.cts +7 -0
  38. package/dist/commands/daemon/stop.d.cts.map +1 -0
  39. package/dist/commands/daemon/stop.d.mts +7 -0
  40. package/dist/commands/daemon/stop.d.mts.map +1 -0
  41. package/dist/commands/daemon/stop.mjs +24 -0
  42. package/dist/commands/daemon/stop.mjs.map +1 -0
  43. package/dist/daemon/daemon-entry.cjs +245 -0
  44. package/dist/daemon/daemon-entry.cjs.map +1 -0
  45. package/dist/daemon/daemon-entry.d.cts +2 -0
  46. package/dist/daemon/daemon-entry.d.cts.map +1 -0
  47. package/dist/daemon/daemon-entry.d.mts +2 -0
  48. package/dist/daemon/daemon-entry.d.mts.map +1 -0
  49. package/dist/daemon/daemon-entry.mjs +243 -0
  50. package/dist/daemon/daemon-entry.mjs.map +1 -0
  51. package/dist/daemon/data-dir.cjs +23 -0
  52. package/dist/daemon/data-dir.cjs.map +1 -0
  53. package/dist/daemon/data-dir.d.cts +14 -0
  54. package/dist/daemon/data-dir.d.cts.map +1 -0
  55. package/dist/daemon/data-dir.d.mts +14 -0
  56. package/dist/daemon/data-dir.d.mts.map +1 -0
  57. package/dist/daemon/data-dir.mjs +19 -0
  58. package/dist/daemon/data-dir.mjs.map +1 -0
  59. package/dist/daemon/types.cjs.map +1 -1
  60. package/dist/daemon/types.d.cts +5 -0
  61. package/dist/daemon/types.d.cts.map +1 -1
  62. package/dist/daemon/types.d.mts +5 -0
  63. package/dist/daemon/types.d.mts.map +1 -1
  64. package/dist/daemon/types.mjs.map +1 -1
  65. package/dist/daemon/wallet-factory.cjs +234 -0
  66. package/dist/daemon/wallet-factory.cjs.map +1 -0
  67. package/dist/daemon/wallet-factory.d.cts +55 -0
  68. package/dist/daemon/wallet-factory.d.cts.map +1 -0
  69. package/dist/daemon/wallet-factory.d.mts +55 -0
  70. package/dist/daemon/wallet-factory.d.mts.map +1 -0
  71. package/dist/daemon/wallet-factory.mjs +230 -0
  72. package/dist/daemon/wallet-factory.mjs.map +1 -0
  73. package/dist/persistence/persistence.cjs.map +1 -1
  74. package/dist/persistence/persistence.d.cts +1 -1
  75. package/dist/persistence/persistence.d.cts.map +1 -1
  76. package/dist/persistence/persistence.d.mts +1 -1
  77. package/dist/persistence/persistence.d.mts.map +1 -1
  78. package/dist/persistence/persistence.mjs.map +1 -1
  79. package/dist/test/run-command.cjs +73 -0
  80. package/dist/test/run-command.cjs.map +1 -0
  81. package/dist/test/run-command.d.cts +27 -0
  82. package/dist/test/run-command.d.cts.map +1 -0
  83. package/dist/test/run-command.d.mts +27 -0
  84. package/dist/test/run-command.d.mts.map +1 -0
  85. package/dist/test/run-command.mjs +69 -0
  86. package/dist/test/run-command.mjs.map +1 -0
  87. package/package.json +5 -1
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.cjs","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StateMetadataConstraint } from '@metamask/base-controller';\nimport { hasProperty } from '@metamask/utils';\nimport type { Json } from '@metamask/utils';\nimport type {\n DefaultActions,\n DefaultEvents,\n RootMessenger,\n} from '@metamask/wallet';\nimport type { Patch } from 'immer';\n\nimport type { KeyValueStore } from './KeyValueStore';\n\n/**\n * Handler for a controller's `stateChanged` event: the new controller state and\n * the Immer patches describing what changed.\n */\ntype StateChangedHandler = (\n state: Record<string, Json>,\n patches: Patch[],\n) => void;\n\n/**\n * Construct a store key from a controller name and property name.\n *\n * @param controllerName - The controller name.\n * @param propertyName - The property name.\n * @returns The store key in the format `ControllerName.propertyName`.\n */\nfunction storeKey(controllerName: string, propertyName: string): string {\n return `${controllerName}.${propertyName}`;\n}\n\n/**\n * Load persisted state from the key-value store and reconstruct it as\n * a record keyed by controller name.\n *\n * Keys in the store follow the format `ControllerName.propertyName`.\n * This function groups them into `{ [controllerName]: { [propertyName]: value } }`.\n *\n * Only properties that are currently persist-flagged in `controllerMetadata`\n * are rehydrated. Rows for controllers or properties that no longer exist — or\n * whose `persist` flag has since been disabled — are ignored. This keeps\n * loading symmetric with {@link subscribeToChanges}, which only ever writes\n * persist-flagged properties: without the filter, a migration that stops\n * persisting a property would leave its stale row on disk to be resurrected\n * into the `Wallet` constructor state on the next restart.\n *\n * @param store - The key-value store to read from.\n * @param controllerMetadata - A map from controller name to its state metadata,\n * used to filter out keys that are no longer persist-flagged.\n * @returns A record of controller states, keyed by controller name, suitable\n * for the `state` option of the `Wallet` constructor.\n */\nexport function loadState(\n store: KeyValueStore,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n): Record<string, Record<string, Json>> {\n const allPairs = store.getAll();\n const state: Record<string, Record<string, Json>> = {};\n\n for (const [key, value] of Object.entries(allPairs)) {\n const dotIndex = key.indexOf('.');\n if (dotIndex === -1) {\n throw new Error(\n `Invalid key in store: '${key}'. Expected format 'ControllerName.propertyName'.`,\n );\n }\n const controllerName = key.slice(0, dotIndex);\n const propertyName = key.slice(dotIndex + 1);\n\n if (!controllerName || !propertyName) {\n throw new Error(\n `Invalid key in store: '${key}'. Both controller name and property name must be non-empty.`,\n );\n }\n\n if (!isPersisted(controllerMetadata[controllerName], propertyName)) {\n continue;\n }\n\n if (!state[controllerName]) {\n state[controllerName] = {};\n }\n state[controllerName][propertyName] = value;\n }\n return state;\n}\n\n/**\n * Subscribe to all controller `stateChanged` events and persist changes\n * to the key-value store.\n *\n * For each controller's metadata, this function determines which state\n * properties are persist-flagged. When a `stateChanged` event fires, it uses\n * the Immer patches to identify which top-level properties changed, filters\n * to only persist-flagged properties, and writes them to the store.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param controllerMetadata - A map from controller name to its state metadata.\n * @param store - The key-value store to write to.\n * @param log - Optional logger for persistence-write failures. Defaults to\n * `console.error` when omitted. A daemon host should supply its own logger,\n * since a backgrounded daemon's stdio may be discarded.\n * @returns A function that unsubscribes all persistence handlers.\n */\nexport function subscribeToChanges(\n messenger: RootMessenger<DefaultActions, DefaultEvents>,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n store: KeyValueStore,\n log?: (message: string) => void,\n): () => void {\n const unsubscribers: (() => void)[] = [];\n const logFn =\n log ??\n ((message: string): void => {\n console.error(message);\n });\n\n for (const [controllerName, metadata] of Object.entries(controllerMetadata)) {\n const persistedProperties = getPersistPropertyNames(metadata);\n if (persistedProperties.size === 0) {\n continue;\n }\n\n const eventType = `${controllerName}:stateChanged`;\n\n const handler: StateChangedHandler = (state, patches) => {\n const changed = getChangedProperties(patches, persistedProperties);\n\n for (const prop of changed) {\n const key = storeKey(controllerName, prop);\n const removed = !hasProperty(state, prop);\n\n // Derive the value before the try/catch so a throwing `StateDeriver`\n // surfaces as its own error instead of a misreported write failure.\n const persistFlag = metadata[prop]?.persist;\n const value =\n !removed && typeof persistFlag === 'function'\n ? persistFlag(state[prop] as never)\n : state[prop];\n\n try {\n if (removed) {\n store.delete(key);\n } else {\n store.set(key, value);\n }\n } catch (error) {\n // TODO: Surface persistence-write failures up the stack so callers\n // can decide to halt rather than continue with diverging in-memory\n // and on-disk state. For now, log and continue.\n logFn(`Failed to persist state for ${key}: ${String(error)}`);\n }\n }\n };\n\n unsubscribers.push(subscribeToStateChanged(messenger, eventType, handler));\n }\n\n const unsubscribeAll = (): void => {\n while (unsubscribers.length > 0) {\n unsubscribers.pop()?.();\n }\n };\n\n return unsubscribeAll;\n}\n\n/**\n * Subscribe a handler to a controller's `stateChanged` event.\n *\n * The event name is built from a runtime controller name, so it widens to\n * `string` and cannot be proven to be a literal member of the messenger's event\n * union at compile time. This helper localizes that single unavoidable cast\n * behind a typed {@link StateChangedHandler}, so the `(state, patches)` payload\n * shape stays compile-checked at every call site instead of being erased by a\n * statement-level `@ts-expect-error`.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param eventType - The `${controllerName}:stateChanged` event name.\n * @param handler - The state-change handler to register.\n * @returns A function that unsubscribes the handler.\n */\nfunction subscribeToStateChanged(\n messenger: RootMessenger<DefaultActions, DefaultEvents>,\n eventType: string,\n handler: StateChangedHandler,\n): () => void {\n const subscriber = messenger as unknown as {\n subscribe: (eventType: string, handler: StateChangedHandler) => void;\n unsubscribe: (eventType: string, handler: StateChangedHandler) => void;\n };\n subscriber.subscribe(eventType, handler);\n return () => {\n subscriber.unsubscribe(eventType, handler);\n };\n}\n\n/**\n * Determine whether a property is currently persist-flagged.\n *\n * The `persist` flag is truthy when it is `true` or a `StateDeriver` function,\n * and falsy when it is `false` or when the controller or property is absent\n * from the metadata. `loadState` and `subscribeToChanges` share this predicate\n * so the read and write paths can never disagree on what counts as persisted.\n *\n * @param metadata - The controller's state metadata, or `undefined` when the\n * controller is absent from the metadata map.\n * @param propertyName - The property name to check.\n * @returns `true` if the property should be persisted.\n */\nfunction isPersisted(\n metadata: Readonly<StateMetadataConstraint> | undefined,\n propertyName: string,\n): boolean {\n return Boolean(metadata?.[propertyName]?.persist);\n}\n\n/**\n * Get the set of property names whose `persist` metadata is truthy\n * (either `true` or a `StateDeriver` function).\n *\n * @param metadata - The controller's state metadata.\n * @returns A set of property names that should be persisted.\n */\nfunction getPersistPropertyNames(\n metadata: StateMetadataConstraint,\n): ReadonlySet<string> {\n const names = new Set<string>();\n for (const key of Object.keys(metadata)) {\n if (isPersisted(metadata, key)) {\n names.add(key);\n }\n }\n return names;\n}\n\n/**\n * Extracts the set of persist-flagged top-level property names that changed\n * from an array of Immer patches.\n *\n * If any patch has an empty path (indicating a root state replacement),\n * all persist-flagged properties are returned.\n *\n * @param patches - Immer patches from a state update.\n * @param persistedProperties - The set of persist-flagged property names.\n * @returns A set of top-level property names that were modified.\n */\nfunction getChangedProperties(\n patches: Patch[],\n persistedProperties: ReadonlySet<string>,\n): ReadonlySet<string> {\n const changed = new Set<string>();\n for (const patch of patches) {\n if (patch.path.length === 0) {\n return persistedProperties;\n }\n\n const prop = String(patch.path[0]);\n if (persistedProperties.has(prop)) {\n changed.add(prop);\n }\n }\n return changed;\n}\n"]}
1
+ {"version":3,"file":"persistence.cjs","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StateMetadataConstraint } from '@metamask/base-controller';\nimport { hasProperty } from '@metamask/utils';\nimport type { Json } from '@metamask/utils';\nimport type {\n DefaultActions,\n DefaultEvents,\n RootMessenger,\n} from '@metamask/wallet';\nimport type { Patch } from 'immer';\n\nimport type { KeyValueStore } from './KeyValueStore';\n\n/**\n * Handler for a controller's `stateChanged` event: the new controller state and\n * the Immer patches describing what changed.\n */\ntype StateChangedHandler = (\n state: Record<string, Json>,\n patches: Patch[],\n) => void;\n\n/**\n * Construct a store key from a controller name and property name.\n *\n * @param controllerName - The controller name.\n * @param propertyName - The property name.\n * @returns The store key in the format `ControllerName.propertyName`.\n */\nfunction storeKey(controllerName: string, propertyName: string): string {\n return `${controllerName}.${propertyName}`;\n}\n\n/**\n * Load persisted state from the key-value store and reconstruct it as\n * a record keyed by controller name.\n *\n * Keys in the store follow the format `ControllerName.propertyName`.\n * This function groups them into `{ [controllerName]: { [propertyName]: value } }`.\n *\n * Only properties that are currently persist-flagged in `controllerMetadata`\n * are rehydrated. Rows for controllers or properties that no longer exist — or\n * whose `persist` flag has since been disabled — are ignored. This keeps\n * loading symmetric with {@link subscribeToChanges}, which only ever writes\n * persist-flagged properties: without the filter, a migration that stops\n * persisting a property would leave its stale row on disk to be resurrected\n * into the `Wallet` constructor state on the next restart.\n *\n * @param store - The key-value store to read from.\n * @param controllerMetadata - A map from controller name to its state metadata,\n * used to filter out keys that are no longer persist-flagged.\n * @returns A record of controller states, keyed by controller name, suitable\n * for the `state` option of the `Wallet` constructor.\n */\nexport function loadState(\n store: KeyValueStore,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n): Record<string, Record<string, Json>> {\n const allPairs = store.getAll();\n const state: Record<string, Record<string, Json>> = {};\n\n for (const [key, value] of Object.entries(allPairs)) {\n const dotIndex = key.indexOf('.');\n if (dotIndex === -1) {\n throw new Error(\n `Invalid key in store: '${key}'. Expected format 'ControllerName.propertyName'.`,\n );\n }\n const controllerName = key.slice(0, dotIndex);\n const propertyName = key.slice(dotIndex + 1);\n\n if (!controllerName || !propertyName) {\n throw new Error(\n `Invalid key in store: '${key}'. Both controller name and property name must be non-empty.`,\n );\n }\n\n if (!isPersisted(controllerMetadata[controllerName], propertyName)) {\n continue;\n }\n\n if (!state[controllerName]) {\n state[controllerName] = {};\n }\n state[controllerName][propertyName] = value;\n }\n return state;\n}\n\n/**\n * Subscribe to all controller `stateChanged` events and persist changes\n * to the key-value store.\n *\n * For each controller's metadata, this function determines which state\n * properties are persist-flagged. When a `stateChanged` event fires, it uses\n * the Immer patches to identify which top-level properties changed, filters\n * to only persist-flagged properties, and writes them to the store.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param controllerMetadata - A map from controller name to its state metadata.\n * @param store - The key-value store to write to.\n * @param log - Optional logger for persistence-write failures. Defaults to\n * `console.error` when omitted. A daemon host should supply its own logger,\n * since a backgrounded daemon's stdio may be discarded.\n * @returns A function that unsubscribes all persistence handlers.\n */\nexport function subscribeToChanges(\n messenger: Readonly<RootMessenger<DefaultActions, DefaultEvents>>,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n store: KeyValueStore,\n log?: (message: string) => void,\n): () => void {\n const unsubscribers: (() => void)[] = [];\n const logFn =\n log ??\n ((message: string): void => {\n console.error(message);\n });\n\n for (const [controllerName, metadata] of Object.entries(controllerMetadata)) {\n const persistedProperties = getPersistPropertyNames(metadata);\n if (persistedProperties.size === 0) {\n continue;\n }\n\n const eventType = `${controllerName}:stateChanged`;\n\n const handler: StateChangedHandler = (state, patches) => {\n const changed = getChangedProperties(patches, persistedProperties);\n\n for (const prop of changed) {\n const key = storeKey(controllerName, prop);\n const removed = !hasProperty(state, prop);\n\n // Derive the value before the try/catch so a throwing `StateDeriver`\n // surfaces as its own error instead of a misreported write failure.\n const persistFlag = metadata[prop]?.persist;\n const value =\n !removed && typeof persistFlag === 'function'\n ? persistFlag(state[prop] as never)\n : state[prop];\n\n try {\n if (removed) {\n store.delete(key);\n } else {\n store.set(key, value);\n }\n } catch (error) {\n // TODO: Surface persistence-write failures up the stack so callers\n // can decide to halt rather than continue with diverging in-memory\n // and on-disk state. For now, log and continue.\n logFn(`Failed to persist state for ${key}: ${String(error)}`);\n }\n }\n };\n\n unsubscribers.push(subscribeToStateChanged(messenger, eventType, handler));\n }\n\n const unsubscribeAll = (): void => {\n while (unsubscribers.length > 0) {\n unsubscribers.pop()?.();\n }\n };\n\n return unsubscribeAll;\n}\n\n/**\n * Subscribe a handler to a controller's `stateChanged` event.\n *\n * The event name is built from a runtime controller name, so it widens to\n * `string` and cannot be proven to be a literal member of the messenger's event\n * union at compile time. This helper localizes that single unavoidable cast\n * behind a typed {@link StateChangedHandler}, so the `(state, patches)` payload\n * shape stays compile-checked at every call site instead of being erased by a\n * statement-level `@ts-expect-error`.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param eventType - The `${controllerName}:stateChanged` event name.\n * @param handler - The state-change handler to register.\n * @returns A function that unsubscribes the handler.\n */\nfunction subscribeToStateChanged(\n messenger: Readonly<RootMessenger<DefaultActions, DefaultEvents>>,\n eventType: string,\n handler: StateChangedHandler,\n): () => void {\n const subscriber = messenger as unknown as {\n subscribe: (eventType: string, handler: StateChangedHandler) => void;\n unsubscribe: (eventType: string, handler: StateChangedHandler) => void;\n };\n subscriber.subscribe(eventType, handler);\n return () => {\n subscriber.unsubscribe(eventType, handler);\n };\n}\n\n/**\n * Determine whether a property is currently persist-flagged.\n *\n * The `persist` flag is truthy when it is `true` or a `StateDeriver` function,\n * and falsy when it is `false` or when the controller or property is absent\n * from the metadata. `loadState` and `subscribeToChanges` share this predicate\n * so the read and write paths can never disagree on what counts as persisted.\n *\n * @param metadata - The controller's state metadata, or `undefined` when the\n * controller is absent from the metadata map.\n * @param propertyName - The property name to check.\n * @returns `true` if the property should be persisted.\n */\nfunction isPersisted(\n metadata: Readonly<StateMetadataConstraint> | undefined,\n propertyName: string,\n): boolean {\n return Boolean(metadata?.[propertyName]?.persist);\n}\n\n/**\n * Get the set of property names whose `persist` metadata is truthy\n * (either `true` or a `StateDeriver` function).\n *\n * @param metadata - The controller's state metadata.\n * @returns A set of property names that should be persisted.\n */\nfunction getPersistPropertyNames(\n metadata: StateMetadataConstraint,\n): ReadonlySet<string> {\n const names = new Set<string>();\n for (const key of Object.keys(metadata)) {\n if (isPersisted(metadata, key)) {\n names.add(key);\n }\n }\n return names;\n}\n\n/**\n * Extracts the set of persist-flagged top-level property names that changed\n * from an array of Immer patches.\n *\n * If any patch has an empty path (indicating a root state replacement),\n * all persist-flagged properties are returned.\n *\n * @param patches - Immer patches from a state update.\n * @param persistedProperties - The set of persist-flagged property names.\n * @returns A set of top-level property names that were modified.\n */\nfunction getChangedProperties(\n patches: Patch[],\n persistedProperties: ReadonlySet<string>,\n): ReadonlySet<string> {\n const changed = new Set<string>();\n for (const patch of patches) {\n if (patch.path.length === 0) {\n return persistedProperties;\n }\n\n const prop = String(patch.path[0]);\n if (persistedProperties.has(prop)) {\n changed.add(prop);\n }\n }\n return changed;\n}\n"]}
@@ -41,5 +41,5 @@ export declare function loadState(store: KeyValueStore, controllerMetadata: Read
41
41
  * since a backgrounded daemon's stdio may be discarded.
42
42
  * @returns A function that unsubscribes all persistence handlers.
43
43
  */
44
- export declare function subscribeToChanges(messenger: RootMessenger<DefaultActions, DefaultEvents>, controllerMetadata: Readonly<Record<string, Readonly<StateMetadataConstraint>>>, store: KeyValueStore, log?: (message: string) => void): () => void;
44
+ export declare function subscribeToChanges(messenger: Readonly<RootMessenger<DefaultActions, DefaultEvents>>, controllerMetadata: Readonly<Record<string, Readonly<StateMetadataConstraint>>>, store: KeyValueStore, log?: (message: string) => void): () => void;
45
45
  //# sourceMappingURL=persistence.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.d.cts","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,kCAAkC;AAEzE,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAC5C,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACd,yBAAyB;AAG1B,OAAO,KAAK,EAAE,aAAa,EAAE,4BAAwB;AAsBrD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,aAAa,EACpB,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,GACA,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CA8BtC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,aAAa,CAAC,cAAc,EAAE,aAAa,CAAC,EACvD,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,EACD,KAAK,EAAE,aAAa,EACpB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,MAAM,IAAI,CAwDZ"}
1
+ {"version":3,"file":"persistence.d.cts","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,kCAAkC;AAEzE,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAC5C,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACd,yBAAyB;AAG1B,OAAO,KAAK,EAAE,aAAa,EAAE,4BAAwB;AAsBrD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,aAAa,EACpB,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,GACA,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CA8BtC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,QAAQ,CAAC,aAAa,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC,EACjE,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,EACD,KAAK,EAAE,aAAa,EACpB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,MAAM,IAAI,CAwDZ"}
@@ -41,5 +41,5 @@ export declare function loadState(store: KeyValueStore, controllerMetadata: Read
41
41
  * since a backgrounded daemon's stdio may be discarded.
42
42
  * @returns A function that unsubscribes all persistence handlers.
43
43
  */
44
- export declare function subscribeToChanges(messenger: RootMessenger<DefaultActions, DefaultEvents>, controllerMetadata: Readonly<Record<string, Readonly<StateMetadataConstraint>>>, store: KeyValueStore, log?: (message: string) => void): () => void;
44
+ export declare function subscribeToChanges(messenger: Readonly<RootMessenger<DefaultActions, DefaultEvents>>, controllerMetadata: Readonly<Record<string, Readonly<StateMetadataConstraint>>>, store: KeyValueStore, log?: (message: string) => void): () => void;
45
45
  //# sourceMappingURL=persistence.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.d.mts","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,kCAAkC;AAEzE,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAC5C,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACd,yBAAyB;AAG1B,OAAO,KAAK,EAAE,aAAa,EAAE,4BAAwB;AAsBrD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,aAAa,EACpB,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,GACA,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CA8BtC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,aAAa,CAAC,cAAc,EAAE,aAAa,CAAC,EACvD,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,EACD,KAAK,EAAE,aAAa,EACpB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,MAAM,IAAI,CAwDZ"}
1
+ {"version":3,"file":"persistence.d.mts","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,kCAAkC;AAEzE,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAC5C,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACd,yBAAyB;AAG1B,OAAO,KAAK,EAAE,aAAa,EAAE,4BAAwB;AAsBrD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,aAAa,EACpB,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,GACA,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CA8BtC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,QAAQ,CAAC,aAAa,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC,EACjE,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,EACD,KAAK,EAAE,aAAa,EACpB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,MAAM,IAAI,CAwDZ"}
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.mjs","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,wBAAwB;AAoB9C;;;;;;GAMG;AACH,SAAS,QAAQ,CAAC,cAAsB,EAAE,YAAoB;IAC5D,OAAO,GAAG,cAAc,IAAI,YAAY,EAAE,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,SAAS,CACvB,KAAoB,EACpB,kBAEC;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;IAChC,MAAM,KAAK,GAAyC,EAAE,CAAC;IAEvD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACb,0BAA0B,GAAG,mDAAmD,CACjF,CAAC;QACJ,CAAC;QACD,MAAM,cAAc,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC9C,MAAM,YAAY,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;QAE7C,IAAI,CAAC,cAAc,IAAI,CAAC,YAAY,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CACb,0BAA0B,GAAG,8DAA8D,CAC5F,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,cAAc,CAAC,EAAE,YAAY,CAAC,EAAE,CAAC;YACnE,SAAS;QACX,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;YAC3B,KAAK,CAAC,cAAc,CAAC,GAAG,EAAE,CAAC;QAC7B,CAAC;QACD,KAAK,CAAC,cAAc,CAAC,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC;IAC9C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAChC,SAAuD,EACvD,kBAEC,EACD,KAAoB,EACpB,GAA+B;IAE/B,MAAM,aAAa,GAAmB,EAAE,CAAC;IACzC,MAAM,KAAK,GACT,GAAG;QACH,CAAC,CAAC,OAAe,EAAQ,EAAE;YACzB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;IAEL,KAAK,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC5E,MAAM,mBAAmB,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;QAC9D,IAAI,mBAAmB,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACnC,SAAS;QACX,CAAC;QAED,MAAM,SAAS,GAAG,GAAG,cAAc,eAAe,CAAC;QAEnD,MAAM,OAAO,GAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;YACtD,MAAM,OAAO,GAAG,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;YAEnE,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;gBAC3B,MAAM,GAAG,GAAG,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;gBAC3C,MAAM,OAAO,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;gBAE1C,qEAAqE;gBACrE,oEAAoE;gBACpE,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;gBAC5C,MAAM,KAAK,GACT,CAAC,OAAO,IAAI,OAAO,WAAW,KAAK,UAAU;oBAC3C,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAU,CAAC;oBACnC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAElB,IAAI,CAAC;oBACH,IAAI,OAAO,EAAE,CAAC;wBACZ,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;oBACpB,CAAC;yBAAM,CAAC;wBACN,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;oBACxB,CAAC;gBACH,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,mEAAmE;oBACnE,mEAAmE;oBACnE,gDAAgD;oBAChD,KAAK,CAAC,+BAA+B,GAAG,KAAK,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBAChE,CAAC;YACH,CAAC;QACH,CAAC,CAAC;QAEF,aAAa,CAAC,IAAI,CAAC,uBAAuB,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7E,CAAC;IAED,MAAM,cAAc,GAAG,GAAS,EAAE;QAChC,OAAO,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,aAAa,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAS,uBAAuB,CAC9B,SAAuD,EACvD,SAAiB,EACjB,OAA4B;IAE5B,MAAM,UAAU,GAAG,SAGlB,CAAC;IACF,UAAU,CAAC,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACzC,OAAO,GAAG,EAAE;QACV,UAAU,CAAC,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC7C,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAS,WAAW,CAClB,QAAuD,EACvD,YAAoB;IAEpB,OAAO,OAAO,CAAC,QAAQ,EAAE,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;AACpD,CAAC;AAED;;;;;;GAMG;AACH,SAAS,uBAAuB,CAC9B,QAAiC;IAEjC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxC,IAAI,WAAW,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC;YAC/B,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,oBAAoB,CAC3B,OAAgB,EAChB,mBAAwC;IAExC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,OAAO,mBAAmB,CAAC;QAC7B,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["import type { StateMetadataConstraint } from '@metamask/base-controller';\nimport { hasProperty } from '@metamask/utils';\nimport type { Json } from '@metamask/utils';\nimport type {\n DefaultActions,\n DefaultEvents,\n RootMessenger,\n} from '@metamask/wallet';\nimport type { Patch } from 'immer';\n\nimport type { KeyValueStore } from './KeyValueStore';\n\n/**\n * Handler for a controller's `stateChanged` event: the new controller state and\n * the Immer patches describing what changed.\n */\ntype StateChangedHandler = (\n state: Record<string, Json>,\n patches: Patch[],\n) => void;\n\n/**\n * Construct a store key from a controller name and property name.\n *\n * @param controllerName - The controller name.\n * @param propertyName - The property name.\n * @returns The store key in the format `ControllerName.propertyName`.\n */\nfunction storeKey(controllerName: string, propertyName: string): string {\n return `${controllerName}.${propertyName}`;\n}\n\n/**\n * Load persisted state from the key-value store and reconstruct it as\n * a record keyed by controller name.\n *\n * Keys in the store follow the format `ControllerName.propertyName`.\n * This function groups them into `{ [controllerName]: { [propertyName]: value } }`.\n *\n * Only properties that are currently persist-flagged in `controllerMetadata`\n * are rehydrated. Rows for controllers or properties that no longer exist — or\n * whose `persist` flag has since been disabled — are ignored. This keeps\n * loading symmetric with {@link subscribeToChanges}, which only ever writes\n * persist-flagged properties: without the filter, a migration that stops\n * persisting a property would leave its stale row on disk to be resurrected\n * into the `Wallet` constructor state on the next restart.\n *\n * @param store - The key-value store to read from.\n * @param controllerMetadata - A map from controller name to its state metadata,\n * used to filter out keys that are no longer persist-flagged.\n * @returns A record of controller states, keyed by controller name, suitable\n * for the `state` option of the `Wallet` constructor.\n */\nexport function loadState(\n store: KeyValueStore,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n): Record<string, Record<string, Json>> {\n const allPairs = store.getAll();\n const state: Record<string, Record<string, Json>> = {};\n\n for (const [key, value] of Object.entries(allPairs)) {\n const dotIndex = key.indexOf('.');\n if (dotIndex === -1) {\n throw new Error(\n `Invalid key in store: '${key}'. Expected format 'ControllerName.propertyName'.`,\n );\n }\n const controllerName = key.slice(0, dotIndex);\n const propertyName = key.slice(dotIndex + 1);\n\n if (!controllerName || !propertyName) {\n throw new Error(\n `Invalid key in store: '${key}'. Both controller name and property name must be non-empty.`,\n );\n }\n\n if (!isPersisted(controllerMetadata[controllerName], propertyName)) {\n continue;\n }\n\n if (!state[controllerName]) {\n state[controllerName] = {};\n }\n state[controllerName][propertyName] = value;\n }\n return state;\n}\n\n/**\n * Subscribe to all controller `stateChanged` events and persist changes\n * to the key-value store.\n *\n * For each controller's metadata, this function determines which state\n * properties are persist-flagged. When a `stateChanged` event fires, it uses\n * the Immer patches to identify which top-level properties changed, filters\n * to only persist-flagged properties, and writes them to the store.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param controllerMetadata - A map from controller name to its state metadata.\n * @param store - The key-value store to write to.\n * @param log - Optional logger for persistence-write failures. Defaults to\n * `console.error` when omitted. A daemon host should supply its own logger,\n * since a backgrounded daemon's stdio may be discarded.\n * @returns A function that unsubscribes all persistence handlers.\n */\nexport function subscribeToChanges(\n messenger: RootMessenger<DefaultActions, DefaultEvents>,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n store: KeyValueStore,\n log?: (message: string) => void,\n): () => void {\n const unsubscribers: (() => void)[] = [];\n const logFn =\n log ??\n ((message: string): void => {\n console.error(message);\n });\n\n for (const [controllerName, metadata] of Object.entries(controllerMetadata)) {\n const persistedProperties = getPersistPropertyNames(metadata);\n if (persistedProperties.size === 0) {\n continue;\n }\n\n const eventType = `${controllerName}:stateChanged`;\n\n const handler: StateChangedHandler = (state, patches) => {\n const changed = getChangedProperties(patches, persistedProperties);\n\n for (const prop of changed) {\n const key = storeKey(controllerName, prop);\n const removed = !hasProperty(state, prop);\n\n // Derive the value before the try/catch so a throwing `StateDeriver`\n // surfaces as its own error instead of a misreported write failure.\n const persistFlag = metadata[prop]?.persist;\n const value =\n !removed && typeof persistFlag === 'function'\n ? persistFlag(state[prop] as never)\n : state[prop];\n\n try {\n if (removed) {\n store.delete(key);\n } else {\n store.set(key, value);\n }\n } catch (error) {\n // TODO: Surface persistence-write failures up the stack so callers\n // can decide to halt rather than continue with diverging in-memory\n // and on-disk state. For now, log and continue.\n logFn(`Failed to persist state for ${key}: ${String(error)}`);\n }\n }\n };\n\n unsubscribers.push(subscribeToStateChanged(messenger, eventType, handler));\n }\n\n const unsubscribeAll = (): void => {\n while (unsubscribers.length > 0) {\n unsubscribers.pop()?.();\n }\n };\n\n return unsubscribeAll;\n}\n\n/**\n * Subscribe a handler to a controller's `stateChanged` event.\n *\n * The event name is built from a runtime controller name, so it widens to\n * `string` and cannot be proven to be a literal member of the messenger's event\n * union at compile time. This helper localizes that single unavoidable cast\n * behind a typed {@link StateChangedHandler}, so the `(state, patches)` payload\n * shape stays compile-checked at every call site instead of being erased by a\n * statement-level `@ts-expect-error`.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param eventType - The `${controllerName}:stateChanged` event name.\n * @param handler - The state-change handler to register.\n * @returns A function that unsubscribes the handler.\n */\nfunction subscribeToStateChanged(\n messenger: RootMessenger<DefaultActions, DefaultEvents>,\n eventType: string,\n handler: StateChangedHandler,\n): () => void {\n const subscriber = messenger as unknown as {\n subscribe: (eventType: string, handler: StateChangedHandler) => void;\n unsubscribe: (eventType: string, handler: StateChangedHandler) => void;\n };\n subscriber.subscribe(eventType, handler);\n return () => {\n subscriber.unsubscribe(eventType, handler);\n };\n}\n\n/**\n * Determine whether a property is currently persist-flagged.\n *\n * The `persist` flag is truthy when it is `true` or a `StateDeriver` function,\n * and falsy when it is `false` or when the controller or property is absent\n * from the metadata. `loadState` and `subscribeToChanges` share this predicate\n * so the read and write paths can never disagree on what counts as persisted.\n *\n * @param metadata - The controller's state metadata, or `undefined` when the\n * controller is absent from the metadata map.\n * @param propertyName - The property name to check.\n * @returns `true` if the property should be persisted.\n */\nfunction isPersisted(\n metadata: Readonly<StateMetadataConstraint> | undefined,\n propertyName: string,\n): boolean {\n return Boolean(metadata?.[propertyName]?.persist);\n}\n\n/**\n * Get the set of property names whose `persist` metadata is truthy\n * (either `true` or a `StateDeriver` function).\n *\n * @param metadata - The controller's state metadata.\n * @returns A set of property names that should be persisted.\n */\nfunction getPersistPropertyNames(\n metadata: StateMetadataConstraint,\n): ReadonlySet<string> {\n const names = new Set<string>();\n for (const key of Object.keys(metadata)) {\n if (isPersisted(metadata, key)) {\n names.add(key);\n }\n }\n return names;\n}\n\n/**\n * Extracts the set of persist-flagged top-level property names that changed\n * from an array of Immer patches.\n *\n * If any patch has an empty path (indicating a root state replacement),\n * all persist-flagged properties are returned.\n *\n * @param patches - Immer patches from a state update.\n * @param persistedProperties - The set of persist-flagged property names.\n * @returns A set of top-level property names that were modified.\n */\nfunction getChangedProperties(\n patches: Patch[],\n persistedProperties: ReadonlySet<string>,\n): ReadonlySet<string> {\n const changed = new Set<string>();\n for (const patch of patches) {\n if (patch.path.length === 0) {\n return persistedProperties;\n }\n\n const prop = String(patch.path[0]);\n if (persistedProperties.has(prop)) {\n changed.add(prop);\n }\n }\n return changed;\n}\n"]}
1
+ {"version":3,"file":"persistence.mjs","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StateMetadataConstraint } from '@metamask/base-controller';\nimport { hasProperty } from '@metamask/utils';\nimport type { Json } from '@metamask/utils';\nimport type {\n DefaultActions,\n DefaultEvents,\n RootMessenger,\n} from '@metamask/wallet';\nimport type { Patch } from 'immer';\n\nimport type { KeyValueStore } from './KeyValueStore';\n\n/**\n * Handler for a controller's `stateChanged` event: the new controller state and\n * the Immer patches describing what changed.\n */\ntype StateChangedHandler = (\n state: Record<string, Json>,\n patches: Patch[],\n) => void;\n\n/**\n * Construct a store key from a controller name and property name.\n *\n * @param controllerName - The controller name.\n * @param propertyName - The property name.\n * @returns The store key in the format `ControllerName.propertyName`.\n */\nfunction storeKey(controllerName: string, propertyName: string): string {\n return `${controllerName}.${propertyName}`;\n}\n\n/**\n * Load persisted state from the key-value store and reconstruct it as\n * a record keyed by controller name.\n *\n * Keys in the store follow the format `ControllerName.propertyName`.\n * This function groups them into `{ [controllerName]: { [propertyName]: value } }`.\n *\n * Only properties that are currently persist-flagged in `controllerMetadata`\n * are rehydrated. Rows for controllers or properties that no longer exist — or\n * whose `persist` flag has since been disabled — are ignored. This keeps\n * loading symmetric with {@link subscribeToChanges}, which only ever writes\n * persist-flagged properties: without the filter, a migration that stops\n * persisting a property would leave its stale row on disk to be resurrected\n * into the `Wallet` constructor state on the next restart.\n *\n * @param store - The key-value store to read from.\n * @param controllerMetadata - A map from controller name to its state metadata,\n * used to filter out keys that are no longer persist-flagged.\n * @returns A record of controller states, keyed by controller name, suitable\n * for the `state` option of the `Wallet` constructor.\n */\nexport function loadState(\n store: KeyValueStore,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n): Record<string, Record<string, Json>> {\n const allPairs = store.getAll();\n const state: Record<string, Record<string, Json>> = {};\n\n for (const [key, value] of Object.entries(allPairs)) {\n const dotIndex = key.indexOf('.');\n if (dotIndex === -1) {\n throw new Error(\n `Invalid key in store: '${key}'. Expected format 'ControllerName.propertyName'.`,\n );\n }\n const controllerName = key.slice(0, dotIndex);\n const propertyName = key.slice(dotIndex + 1);\n\n if (!controllerName || !propertyName) {\n throw new Error(\n `Invalid key in store: '${key}'. Both controller name and property name must be non-empty.`,\n );\n }\n\n if (!isPersisted(controllerMetadata[controllerName], propertyName)) {\n continue;\n }\n\n if (!state[controllerName]) {\n state[controllerName] = {};\n }\n state[controllerName][propertyName] = value;\n }\n return state;\n}\n\n/**\n * Subscribe to all controller `stateChanged` events and persist changes\n * to the key-value store.\n *\n * For each controller's metadata, this function determines which state\n * properties are persist-flagged. When a `stateChanged` event fires, it uses\n * the Immer patches to identify which top-level properties changed, filters\n * to only persist-flagged properties, and writes them to the store.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param controllerMetadata - A map from controller name to its state metadata.\n * @param store - The key-value store to write to.\n * @param log - Optional logger for persistence-write failures. Defaults to\n * `console.error` when omitted. A daemon host should supply its own logger,\n * since a backgrounded daemon's stdio may be discarded.\n * @returns A function that unsubscribes all persistence handlers.\n */\nexport function subscribeToChanges(\n messenger: Readonly<RootMessenger<DefaultActions, DefaultEvents>>,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n store: KeyValueStore,\n log?: (message: string) => void,\n): () => void {\n const unsubscribers: (() => void)[] = [];\n const logFn =\n log ??\n ((message: string): void => {\n console.error(message);\n });\n\n for (const [controllerName, metadata] of Object.entries(controllerMetadata)) {\n const persistedProperties = getPersistPropertyNames(metadata);\n if (persistedProperties.size === 0) {\n continue;\n }\n\n const eventType = `${controllerName}:stateChanged`;\n\n const handler: StateChangedHandler = (state, patches) => {\n const changed = getChangedProperties(patches, persistedProperties);\n\n for (const prop of changed) {\n const key = storeKey(controllerName, prop);\n const removed = !hasProperty(state, prop);\n\n // Derive the value before the try/catch so a throwing `StateDeriver`\n // surfaces as its own error instead of a misreported write failure.\n const persistFlag = metadata[prop]?.persist;\n const value =\n !removed && typeof persistFlag === 'function'\n ? persistFlag(state[prop] as never)\n : state[prop];\n\n try {\n if (removed) {\n store.delete(key);\n } else {\n store.set(key, value);\n }\n } catch (error) {\n // TODO: Surface persistence-write failures up the stack so callers\n // can decide to halt rather than continue with diverging in-memory\n // and on-disk state. For now, log and continue.\n logFn(`Failed to persist state for ${key}: ${String(error)}`);\n }\n }\n };\n\n unsubscribers.push(subscribeToStateChanged(messenger, eventType, handler));\n }\n\n const unsubscribeAll = (): void => {\n while (unsubscribers.length > 0) {\n unsubscribers.pop()?.();\n }\n };\n\n return unsubscribeAll;\n}\n\n/**\n * Subscribe a handler to a controller's `stateChanged` event.\n *\n * The event name is built from a runtime controller name, so it widens to\n * `string` and cannot be proven to be a literal member of the messenger's event\n * union at compile time. This helper localizes that single unavoidable cast\n * behind a typed {@link StateChangedHandler}, so the `(state, patches)` payload\n * shape stays compile-checked at every call site instead of being erased by a\n * statement-level `@ts-expect-error`.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param eventType - The `${controllerName}:stateChanged` event name.\n * @param handler - The state-change handler to register.\n * @returns A function that unsubscribes the handler.\n */\nfunction subscribeToStateChanged(\n messenger: Readonly<RootMessenger<DefaultActions, DefaultEvents>>,\n eventType: string,\n handler: StateChangedHandler,\n): () => void {\n const subscriber = messenger as unknown as {\n subscribe: (eventType: string, handler: StateChangedHandler) => void;\n unsubscribe: (eventType: string, handler: StateChangedHandler) => void;\n };\n subscriber.subscribe(eventType, handler);\n return () => {\n subscriber.unsubscribe(eventType, handler);\n };\n}\n\n/**\n * Determine whether a property is currently persist-flagged.\n *\n * The `persist` flag is truthy when it is `true` or a `StateDeriver` function,\n * and falsy when it is `false` or when the controller or property is absent\n * from the metadata. `loadState` and `subscribeToChanges` share this predicate\n * so the read and write paths can never disagree on what counts as persisted.\n *\n * @param metadata - The controller's state metadata, or `undefined` when the\n * controller is absent from the metadata map.\n * @param propertyName - The property name to check.\n * @returns `true` if the property should be persisted.\n */\nfunction isPersisted(\n metadata: Readonly<StateMetadataConstraint> | undefined,\n propertyName: string,\n): boolean {\n return Boolean(metadata?.[propertyName]?.persist);\n}\n\n/**\n * Get the set of property names whose `persist` metadata is truthy\n * (either `true` or a `StateDeriver` function).\n *\n * @param metadata - The controller's state metadata.\n * @returns A set of property names that should be persisted.\n */\nfunction getPersistPropertyNames(\n metadata: StateMetadataConstraint,\n): ReadonlySet<string> {\n const names = new Set<string>();\n for (const key of Object.keys(metadata)) {\n if (isPersisted(metadata, key)) {\n names.add(key);\n }\n }\n return names;\n}\n\n/**\n * Extracts the set of persist-flagged top-level property names that changed\n * from an array of Immer patches.\n *\n * If any patch has an empty path (indicating a root state replacement),\n * all persist-flagged properties are returned.\n *\n * @param patches - Immer patches from a state update.\n * @param persistedProperties - The set of persist-flagged property names.\n * @returns A set of top-level property names that were modified.\n */\nfunction getChangedProperties(\n patches: Patch[],\n persistedProperties: ReadonlySet<string>,\n): ReadonlySet<string> {\n const changed = new Set<string>();\n for (const patch of patches) {\n if (patch.path.length === 0) {\n return persistedProperties;\n }\n\n const prop = String(patch.path[0]);\n if (persistedProperties.has(prop)) {\n changed.add(prop);\n }\n }\n return changed;\n}\n"]}
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCommand = void 0;
4
+ const core_1 = require("@oclif/core");
5
+ const errors_1 = require("@oclif/core/errors");
6
+ const TEST_DATA_DIR = '/tmp/mm-cli-test-data';
7
+ const TEST_PACKAGE_ROOT = '/tmp/mm-cli-test-root';
8
+ /**
9
+ * Invoke an oclif command class with the given argv and return the captured
10
+ * stdout/stderr/error so tests can assert on them.
11
+ *
12
+ * Bypasses `Command.run`'s static plugin-loading path (which requires a real
13
+ * `Config.load`) by constructing the command instance directly with a
14
+ * hand-rolled `Config` and invoking the protected `_run`. Spies on the
15
+ * Command prototype so `this.log` and `this.error` go to local buffers
16
+ * instead of stdout/stderr.
17
+ *
18
+ * @param CommandClass - The command class (a subclass of `@oclif/core` Command).
19
+ * @param argv - Command-line tokens (flags + positional args).
20
+ * @returns Captured stdout, stderr, and any `this.error()` payload.
21
+ */
22
+ async function runCommand(CommandClass, argv = []) {
23
+ let stdout = '';
24
+ let stderr = '';
25
+ let error;
26
+ const fakeConfig = {
27
+ dataDir: TEST_DATA_DIR,
28
+ root: TEST_PACKAGE_ROOT,
29
+ bin: 'mm',
30
+ name: '@metamask/wallet-cli',
31
+ version: '0.0.0-test',
32
+ pjson: { name: '@metamask/wallet-cli', version: '0.0.0-test' },
33
+ findCommand: () => undefined,
34
+ runHook: async () => ({ successes: [], failures: [] }),
35
+ scopedEnvVar: () => undefined,
36
+ scopedEnvVarKey: () => '',
37
+ scopedEnvVarKeys: () => [],
38
+ scopedEnvVarTrue: () => false,
39
+ plugins: new Map(),
40
+ flexibleTaxonomy: false,
41
+ };
42
+ const logSpy = jest
43
+ .spyOn(core_1.Command.prototype, 'log')
44
+ .mockImplementation((message = '') => {
45
+ stdout += `${String(message)}\n`;
46
+ });
47
+ const errorSpy = jest
48
+ .spyOn(core_1.Command.prototype, 'error')
49
+ .mockImplementation((input) => {
50
+ const message = typeof input === 'string' ? input : input.message;
51
+ throw new errors_1.CLIError(message);
52
+ });
53
+ try {
54
+ const instance = new CommandClass(argv, fakeConfig);
55
+ await instance._run();
56
+ }
57
+ catch (caught) {
58
+ if (caught instanceof errors_1.CLIError) {
59
+ error = caught;
60
+ stderr += `${caught.message}\n`;
61
+ }
62
+ else {
63
+ throw caught;
64
+ }
65
+ }
66
+ finally {
67
+ logSpy.mockRestore();
68
+ errorSpy.mockRestore();
69
+ }
70
+ return { stdout, stderr, error };
71
+ }
72
+ exports.runCommand = runCommand;
73
+ //# sourceMappingURL=run-command.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-command.cjs","sourceRoot":"","sources":["../../src/test/run-command.ts"],"names":[],"mappings":";;;AAAA,sCAAsC;AAEtC,+CAA8C;AAS9C,MAAM,aAAa,GAAG,uBAAuB,CAAC;AAC9C,MAAM,iBAAiB,GAAG,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;GAaG;AACI,KAAK,UAAU,UAAU,CAC9B,YAAyB,EACzB,OAAiB,EAAE;IAMnB,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,KAA2B,CAAC;IAEhC,MAAM,UAAU,GAAG;QACjB,OAAO,EAAE,aAAa;QACtB,IAAI,EAAE,iBAAiB;QACvB,GAAG,EAAE,IAAI;QACT,IAAI,EAAE,sBAAsB;QAC5B,OAAO,EAAE,YAAY;QACrB,KAAK,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,YAAY,EAAE;QAC9D,WAAW,EAAE,GAAG,EAAE,CAAC,SAAS;QAC5B,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QACtD,YAAY,EAAE,GAAG,EAAE,CAAC,SAAS;QAC7B,eAAe,EAAE,GAAG,EAAE,CAAC,EAAE;QACzB,gBAAgB,EAAE,GAAG,EAAE,CAAC,EAAE;QAC1B,gBAAgB,EAAE,GAAG,EAAE,CAAC,KAAK;QAC7B,OAAO,EAAE,IAAI,GAAG,EAAE;QAClB,gBAAgB,EAAE,KAAK;KACH,CAAC;IAEvB,MAAM,MAAM,GAAG,IAAI;SAChB,KAAK,CAAC,cAAO,CAAC,SAAS,EAAE,KAAK,CAAC;SAC/B,kBAAkB,CAAC,CAAC,UAAmB,EAAE,EAAE,EAAE;QAC5C,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;IACnC,CAAC,CAAC,CAAC;IACL,MAAM,QAAQ,GAAG,IAAI;SAClB,KAAK,CAAC,cAAO,CAAC,SAAS,EAAE,OAAO,CAAC;SACjC,kBAAkB,CAAC,CAAC,KAAqB,EAAE,EAAE;QAC5C,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;QAClE,MAAM,IAAI,iBAAQ,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEL,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,YAAY,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACpD,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,MAAe,EAAE,CAAC;QACzB,IAAI,MAAM,YAAY,iBAAQ,EAAE,CAAC;YAC/B,KAAK,GAAG,MAAM,CAAC;YACf,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,IAAI,CAAC;QAClC,CAAC;aAAM,CAAC;YACN,MAAM,MAAM,CAAC;QACf,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,WAAW,EAAE,CAAC;QACrB,QAAQ,CAAC,WAAW,EAAE,CAAC;IACzB,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AACnC,CAAC;AAzDD,gCAyDC","sourcesContent":["import { Command } from '@oclif/core';\nimport type { Config } from '@oclif/core';\nimport { CLIError } from '@oclif/core/errors';\n\ntype CommandCtor = new (\n argv: string[],\n config: Config,\n) => Command & {\n _run: () => Promise<unknown>;\n};\n\nconst TEST_DATA_DIR = '/tmp/mm-cli-test-data';\nconst TEST_PACKAGE_ROOT = '/tmp/mm-cli-test-root';\n\n/**\n * Invoke an oclif command class with the given argv and return the captured\n * stdout/stderr/error so tests can assert on them.\n *\n * Bypasses `Command.run`'s static plugin-loading path (which requires a real\n * `Config.load`) by constructing the command instance directly with a\n * hand-rolled `Config` and invoking the protected `_run`. Spies on the\n * Command prototype so `this.log` and `this.error` go to local buffers\n * instead of stdout/stderr.\n *\n * @param CommandClass - The command class (a subclass of `@oclif/core` Command).\n * @param argv - Command-line tokens (flags + positional args).\n * @returns Captured stdout, stderr, and any `this.error()` payload.\n */\nexport async function runCommand(\n CommandClass: CommandCtor,\n argv: string[] = [],\n): Promise<{\n stdout: string;\n stderr: string;\n error: CLIError | undefined;\n}> {\n let stdout = '';\n let stderr = '';\n let error: CLIError | undefined;\n\n const fakeConfig = {\n dataDir: TEST_DATA_DIR,\n root: TEST_PACKAGE_ROOT,\n bin: 'mm',\n name: '@metamask/wallet-cli',\n version: '0.0.0-test',\n pjson: { name: '@metamask/wallet-cli', version: '0.0.0-test' },\n findCommand: () => undefined,\n runHook: async () => ({ successes: [], failures: [] }),\n scopedEnvVar: () => undefined,\n scopedEnvVarKey: () => '',\n scopedEnvVarKeys: () => [],\n scopedEnvVarTrue: () => false,\n plugins: new Map(),\n flexibleTaxonomy: false,\n } as unknown as Config;\n\n const logSpy = jest\n .spyOn(Command.prototype, 'log')\n .mockImplementation((message: unknown = '') => {\n stdout += `${String(message)}\\n`;\n });\n const errorSpy = jest\n .spyOn(Command.prototype, 'error')\n .mockImplementation((input: string | Error) => {\n const message = typeof input === 'string' ? input : input.message;\n throw new CLIError(message);\n });\n\n try {\n const instance = new CommandClass(argv, fakeConfig);\n await instance._run();\n } catch (caught: unknown) {\n if (caught instanceof CLIError) {\n error = caught;\n stderr += `${caught.message}\\n`;\n } else {\n throw caught;\n }\n } finally {\n logSpy.mockRestore();\n errorSpy.mockRestore();\n }\n\n return { stdout, stderr, error };\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import { Command } from "@oclif/core";
2
+ import type { Config } from "@oclif/core";
3
+ import { CLIError } from "@oclif/core/errors";
4
+ type CommandCtor = new (argv: string[], config: Config) => Command & {
5
+ _run: () => Promise<unknown>;
6
+ };
7
+ /**
8
+ * Invoke an oclif command class with the given argv and return the captured
9
+ * stdout/stderr/error so tests can assert on them.
10
+ *
11
+ * Bypasses `Command.run`'s static plugin-loading path (which requires a real
12
+ * `Config.load`) by constructing the command instance directly with a
13
+ * hand-rolled `Config` and invoking the protected `_run`. Spies on the
14
+ * Command prototype so `this.log` and `this.error` go to local buffers
15
+ * instead of stdout/stderr.
16
+ *
17
+ * @param CommandClass - The command class (a subclass of `@oclif/core` Command).
18
+ * @param argv - Command-line tokens (flags + positional args).
19
+ * @returns Captured stdout, stderr, and any `this.error()` payload.
20
+ */
21
+ export declare function runCommand(CommandClass: CommandCtor, argv?: string[]): Promise<{
22
+ stdout: string;
23
+ stderr: string;
24
+ error: CLIError | undefined;
25
+ }>;
26
+ export {};
27
+ //# sourceMappingURL=run-command.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-command.d.cts","sourceRoot":"","sources":["../../src/test/run-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,oBAAoB;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAoB;AAC1C,OAAO,EAAE,QAAQ,EAAE,2BAA2B;AAE9C,KAAK,WAAW,GAAG,KACjB,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,MAAM,KACX,OAAO,GAAG;IACb,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9B,CAAC;AAKF;;;;;;;;;;;;;GAaG;AACH,wBAAsB,UAAU,CAC9B,YAAY,EAAE,WAAW,EACzB,IAAI,GAAE,MAAM,EAAO,GAClB,OAAO,CAAC;IACT,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;CAC7B,CAAC,CAkDD"}
@@ -0,0 +1,27 @@
1
+ import { Command } from "@oclif/core";
2
+ import type { Config } from "@oclif/core";
3
+ import { CLIError } from "@oclif/core/errors";
4
+ type CommandCtor = new (argv: string[], config: Config) => Command & {
5
+ _run: () => Promise<unknown>;
6
+ };
7
+ /**
8
+ * Invoke an oclif command class with the given argv and return the captured
9
+ * stdout/stderr/error so tests can assert on them.
10
+ *
11
+ * Bypasses `Command.run`'s static plugin-loading path (which requires a real
12
+ * `Config.load`) by constructing the command instance directly with a
13
+ * hand-rolled `Config` and invoking the protected `_run`. Spies on the
14
+ * Command prototype so `this.log` and `this.error` go to local buffers
15
+ * instead of stdout/stderr.
16
+ *
17
+ * @param CommandClass - The command class (a subclass of `@oclif/core` Command).
18
+ * @param argv - Command-line tokens (flags + positional args).
19
+ * @returns Captured stdout, stderr, and any `this.error()` payload.
20
+ */
21
+ export declare function runCommand(CommandClass: CommandCtor, argv?: string[]): Promise<{
22
+ stdout: string;
23
+ stderr: string;
24
+ error: CLIError | undefined;
25
+ }>;
26
+ export {};
27
+ //# sourceMappingURL=run-command.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-command.d.mts","sourceRoot":"","sources":["../../src/test/run-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,oBAAoB;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAoB;AAC1C,OAAO,EAAE,QAAQ,EAAE,2BAA2B;AAE9C,KAAK,WAAW,GAAG,KACjB,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,MAAM,KACX,OAAO,GAAG;IACb,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9B,CAAC;AAKF;;;;;;;;;;;;;GAaG;AACH,wBAAsB,UAAU,CAC9B,YAAY,EAAE,WAAW,EACzB,IAAI,GAAE,MAAM,EAAO,GAClB,OAAO,CAAC;IACT,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;CAC7B,CAAC,CAkDD"}
@@ -0,0 +1,69 @@
1
+ import { Command } from "@oclif/core";
2
+ import { CLIError } from "@oclif/core/errors";
3
+ const TEST_DATA_DIR = '/tmp/mm-cli-test-data';
4
+ const TEST_PACKAGE_ROOT = '/tmp/mm-cli-test-root';
5
+ /**
6
+ * Invoke an oclif command class with the given argv and return the captured
7
+ * stdout/stderr/error so tests can assert on them.
8
+ *
9
+ * Bypasses `Command.run`'s static plugin-loading path (which requires a real
10
+ * `Config.load`) by constructing the command instance directly with a
11
+ * hand-rolled `Config` and invoking the protected `_run`. Spies on the
12
+ * Command prototype so `this.log` and `this.error` go to local buffers
13
+ * instead of stdout/stderr.
14
+ *
15
+ * @param CommandClass - The command class (a subclass of `@oclif/core` Command).
16
+ * @param argv - Command-line tokens (flags + positional args).
17
+ * @returns Captured stdout, stderr, and any `this.error()` payload.
18
+ */
19
+ export async function runCommand(CommandClass, argv = []) {
20
+ let stdout = '';
21
+ let stderr = '';
22
+ let error;
23
+ const fakeConfig = {
24
+ dataDir: TEST_DATA_DIR,
25
+ root: TEST_PACKAGE_ROOT,
26
+ bin: 'mm',
27
+ name: '@metamask/wallet-cli',
28
+ version: '0.0.0-test',
29
+ pjson: { name: '@metamask/wallet-cli', version: '0.0.0-test' },
30
+ findCommand: () => undefined,
31
+ runHook: async () => ({ successes: [], failures: [] }),
32
+ scopedEnvVar: () => undefined,
33
+ scopedEnvVarKey: () => '',
34
+ scopedEnvVarKeys: () => [],
35
+ scopedEnvVarTrue: () => false,
36
+ plugins: new Map(),
37
+ flexibleTaxonomy: false,
38
+ };
39
+ const logSpy = jest
40
+ .spyOn(Command.prototype, 'log')
41
+ .mockImplementation((message = '') => {
42
+ stdout += `${String(message)}\n`;
43
+ });
44
+ const errorSpy = jest
45
+ .spyOn(Command.prototype, 'error')
46
+ .mockImplementation((input) => {
47
+ const message = typeof input === 'string' ? input : input.message;
48
+ throw new CLIError(message);
49
+ });
50
+ try {
51
+ const instance = new CommandClass(argv, fakeConfig);
52
+ await instance._run();
53
+ }
54
+ catch (caught) {
55
+ if (caught instanceof CLIError) {
56
+ error = caught;
57
+ stderr += `${caught.message}\n`;
58
+ }
59
+ else {
60
+ throw caught;
61
+ }
62
+ }
63
+ finally {
64
+ logSpy.mockRestore();
65
+ errorSpy.mockRestore();
66
+ }
67
+ return { stdout, stderr, error };
68
+ }
69
+ //# sourceMappingURL=run-command.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-command.mjs","sourceRoot":"","sources":["../../src/test/run-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,oBAAoB;AAEtC,OAAO,EAAE,QAAQ,EAAE,2BAA2B;AAS9C,MAAM,aAAa,GAAG,uBAAuB,CAAC;AAC9C,MAAM,iBAAiB,GAAG,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,YAAyB,EACzB,OAAiB,EAAE;IAMnB,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,KAA2B,CAAC;IAEhC,MAAM,UAAU,GAAG;QACjB,OAAO,EAAE,aAAa;QACtB,IAAI,EAAE,iBAAiB;QACvB,GAAG,EAAE,IAAI;QACT,IAAI,EAAE,sBAAsB;QAC5B,OAAO,EAAE,YAAY;QACrB,KAAK,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,YAAY,EAAE;QAC9D,WAAW,EAAE,GAAG,EAAE,CAAC,SAAS;QAC5B,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QACtD,YAAY,EAAE,GAAG,EAAE,CAAC,SAAS;QAC7B,eAAe,EAAE,GAAG,EAAE,CAAC,EAAE;QACzB,gBAAgB,EAAE,GAAG,EAAE,CAAC,EAAE;QAC1B,gBAAgB,EAAE,GAAG,EAAE,CAAC,KAAK;QAC7B,OAAO,EAAE,IAAI,GAAG,EAAE;QAClB,gBAAgB,EAAE,KAAK;KACH,CAAC;IAEvB,MAAM,MAAM,GAAG,IAAI;SAChB,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC;SAC/B,kBAAkB,CAAC,CAAC,UAAmB,EAAE,EAAE,EAAE;QAC5C,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;IACnC,CAAC,CAAC,CAAC;IACL,MAAM,QAAQ,GAAG,IAAI;SAClB,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC;SACjC,kBAAkB,CAAC,CAAC,KAAqB,EAAE,EAAE;QAC5C,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;QAClE,MAAM,IAAI,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEL,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,YAAY,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACpD,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,MAAe,EAAE,CAAC;QACzB,IAAI,MAAM,YAAY,QAAQ,EAAE,CAAC;YAC/B,KAAK,GAAG,MAAM,CAAC;YACf,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,IAAI,CAAC;QAClC,CAAC;aAAM,CAAC;YACN,MAAM,MAAM,CAAC;QACf,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,WAAW,EAAE,CAAC;QACrB,QAAQ,CAAC,WAAW,EAAE,CAAC;IACzB,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AACnC,CAAC","sourcesContent":["import { Command } from '@oclif/core';\nimport type { Config } from '@oclif/core';\nimport { CLIError } from '@oclif/core/errors';\n\ntype CommandCtor = new (\n argv: string[],\n config: Config,\n) => Command & {\n _run: () => Promise<unknown>;\n};\n\nconst TEST_DATA_DIR = '/tmp/mm-cli-test-data';\nconst TEST_PACKAGE_ROOT = '/tmp/mm-cli-test-root';\n\n/**\n * Invoke an oclif command class with the given argv and return the captured\n * stdout/stderr/error so tests can assert on them.\n *\n * Bypasses `Command.run`'s static plugin-loading path (which requires a real\n * `Config.load`) by constructing the command instance directly with a\n * hand-rolled `Config` and invoking the protected `_run`. Spies on the\n * Command prototype so `this.log` and `this.error` go to local buffers\n * instead of stdout/stderr.\n *\n * @param CommandClass - The command class (a subclass of `@oclif/core` Command).\n * @param argv - Command-line tokens (flags + positional args).\n * @returns Captured stdout, stderr, and any `this.error()` payload.\n */\nexport async function runCommand(\n CommandClass: CommandCtor,\n argv: string[] = [],\n): Promise<{\n stdout: string;\n stderr: string;\n error: CLIError | undefined;\n}> {\n let stdout = '';\n let stderr = '';\n let error: CLIError | undefined;\n\n const fakeConfig = {\n dataDir: TEST_DATA_DIR,\n root: TEST_PACKAGE_ROOT,\n bin: 'mm',\n name: '@metamask/wallet-cli',\n version: '0.0.0-test',\n pjson: { name: '@metamask/wallet-cli', version: '0.0.0-test' },\n findCommand: () => undefined,\n runHook: async () => ({ successes: [], failures: [] }),\n scopedEnvVar: () => undefined,\n scopedEnvVarKey: () => '',\n scopedEnvVarKeys: () => [],\n scopedEnvVarTrue: () => false,\n plugins: new Map(),\n flexibleTaxonomy: false,\n } as unknown as Config;\n\n const logSpy = jest\n .spyOn(Command.prototype, 'log')\n .mockImplementation((message: unknown = '') => {\n stdout += `${String(message)}\\n`;\n });\n const errorSpy = jest\n .spyOn(Command.prototype, 'error')\n .mockImplementation((input: string | Error) => {\n const message = typeof input === 'string' ? input : input.message;\n throw new CLIError(message);\n });\n\n try {\n const instance = new CommandClass(argv, fakeConfig);\n await instance._run();\n } catch (caught: unknown) {\n if (caught instanceof CLIError) {\n error = caught;\n stderr += `${caught.message}\\n`;\n } else {\n throw caught;\n }\n } finally {\n logSpy.mockRestore();\n errorSpy.mockRestore();\n }\n\n return { stdout, stderr, error };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/wallet-cli",
3
- "version": "0.0.0-preview-1132ac340",
3
+ "version": "0.0.0-preview-56b2c8bdd",
4
4
  "description": "The CLI of @metamask/wallet",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -45,7 +45,9 @@
45
45
  "dependencies": {
46
46
  "@inquirer/confirm": "^6.0.11",
47
47
  "@metamask/base-controller": "^9.1.0",
48
+ "@metamask/remote-feature-flag-controller": "^4.2.2",
48
49
  "@metamask/rpc-errors": "^7.0.2",
50
+ "@metamask/storage-service": "^1.0.2",
49
51
  "@metamask/utils": "^11.11.0",
50
52
  "@metamask/wallet": "^5.0.0",
51
53
  "@oclif/core": "^4.10.5",
@@ -59,7 +61,9 @@
59
61
  "@types/jest": "^29.5.14",
60
62
  "deepmerge": "^4.2.2",
61
63
  "jest": "^29.7.0",
64
+ "jest-environment-node": "^29.7.0",
62
65
  "ts-jest": "^29.2.5",
66
+ "tsx": "^4.20.5",
63
67
  "typescript": "~5.3.3"
64
68
  },
65
69
  "oclif": {