First commit
						commit
						d7934fe348
					
				| @ -0,0 +1,5 @@ | ||||
| # CHANGELOG | ||||
| 
 | ||||
| ## 1.2.0 | ||||
| 
 | ||||
| - Add support for Foundry v10 | ||||
| @ -0,0 +1,30 @@ | ||||
| MIT License | ||||
| 
 | ||||
| Copyright (c) 2020 Asacolips Projects / Foundry Mods | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
| 
 | ||||
| This license does not apply to the compendium content listed in this software's | ||||
| "packs" directory. See the README for licensing information for the compendium | ||||
| packs. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
| 
 | ||||
| 
 | ||||
| IMAGE LICENSES | ||||
| 
 | ||||
| anvil-impact.png by Lorc under CC BY 3.0 from game-icons.net | ||||
											
												Binary file not shown.
											
										
									
								| After Width: | Height: | Size: 18 KiB | 
| @ -0,0 +1,366 @@ | ||||
| @import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"); | ||||
| /* Global styles */ | ||||
| .window-app { | ||||
|   font-family: "Roboto", sans-serif; | ||||
| } | ||||
| 
 | ||||
| .rollable:hover, .rollable:focus { | ||||
|   color: #000; | ||||
|   text-shadow: 0 0 10px red; | ||||
|   cursor: pointer; | ||||
| } | ||||
| 
 | ||||
| .grid, | ||||
| .grid-2col { | ||||
|   display: grid; | ||||
|   grid-column: span 2/span 2; | ||||
|   grid-template-columns: repeat(2, minmax(0, 1fr)); | ||||
|   gap: 10px; | ||||
|   margin: 10px 0; | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| .grid-3col { | ||||
|   grid-column: span 3/span 3; | ||||
|   grid-template-columns: repeat(3, minmax(0, 1fr)); | ||||
| } | ||||
| 
 | ||||
| .grid-4col { | ||||
|   grid-column: span 4/span 4; | ||||
|   grid-template-columns: repeat(4, minmax(0, 1fr)); | ||||
| } | ||||
| 
 | ||||
| .grid-5col { | ||||
|   grid-column: span 5/span 5; | ||||
|   grid-template-columns: repeat(5, minmax(0, 1fr)); | ||||
| } | ||||
| 
 | ||||
| .grid-6col { | ||||
|   grid-column: span 6/span 6; | ||||
|   grid-template-columns: repeat(6, minmax(0, 1fr)); | ||||
| } | ||||
| 
 | ||||
| .grid-7col { | ||||
|   grid-column: span 7/span 7; | ||||
|   grid-template-columns: repeat(7, minmax(0, 1fr)); | ||||
| } | ||||
| 
 | ||||
| .grid-8col { | ||||
|   grid-column: span 8/span 8; | ||||
|   grid-template-columns: repeat(8, minmax(0, 1fr)); | ||||
| } | ||||
| 
 | ||||
| .grid-9col { | ||||
|   grid-column: span 9/span 9; | ||||
|   grid-template-columns: repeat(9, minmax(0, 1fr)); | ||||
| } | ||||
| 
 | ||||
| .grid-10col { | ||||
|   grid-column: span 10/span 10; | ||||
|   grid-template-columns: repeat(10, minmax(0, 1fr)); | ||||
| } | ||||
| 
 | ||||
| .grid-11col { | ||||
|   grid-column: span 11/span 11; | ||||
|   grid-template-columns: repeat(11, minmax(0, 1fr)); | ||||
| } | ||||
| 
 | ||||
| .grid-12col { | ||||
|   grid-column: span 12/span 12; | ||||
|   grid-template-columns: repeat(12, minmax(0, 1fr)); | ||||
| } | ||||
| 
 | ||||
| .grid-start-2 { | ||||
|   grid-column-start: 2; | ||||
| } | ||||
| 
 | ||||
| .grid-start-3 { | ||||
|   grid-column-start: 3; | ||||
| } | ||||
| 
 | ||||
| .grid-start-4 { | ||||
|   grid-column-start: 4; | ||||
| } | ||||
| 
 | ||||
| .grid-start-5 { | ||||
|   grid-column-start: 5; | ||||
| } | ||||
| 
 | ||||
| .grid-start-6 { | ||||
|   grid-column-start: 6; | ||||
| } | ||||
| 
 | ||||
| .grid-start-7 { | ||||
|   grid-column-start: 7; | ||||
| } | ||||
| 
 | ||||
| .grid-start-8 { | ||||
|   grid-column-start: 8; | ||||
| } | ||||
| 
 | ||||
| .grid-start-9 { | ||||
|   grid-column-start: 9; | ||||
| } | ||||
| 
 | ||||
| .grid-start-10 { | ||||
|   grid-column-start: 10; | ||||
| } | ||||
| 
 | ||||
| .grid-start-11 { | ||||
|   grid-column-start: 11; | ||||
| } | ||||
| 
 | ||||
| .grid-start-12 { | ||||
|   grid-column-start: 12; | ||||
| } | ||||
| 
 | ||||
| .grid-span-2 { | ||||
|   grid-column-end: span 2; | ||||
| } | ||||
| 
 | ||||
| .grid-span-3 { | ||||
|   grid-column-end: span 3; | ||||
| } | ||||
| 
 | ||||
| .grid-span-4 { | ||||
|   grid-column-end: span 4; | ||||
| } | ||||
| 
 | ||||
| .grid-span-5 { | ||||
|   grid-column-end: span 5; | ||||
| } | ||||
| 
 | ||||
| .grid-span-6 { | ||||
|   grid-column-end: span 6; | ||||
| } | ||||
| 
 | ||||
| .grid-span-7 { | ||||
|   grid-column-end: span 7; | ||||
| } | ||||
| 
 | ||||
| .grid-span-8 { | ||||
|   grid-column-end: span 8; | ||||
| } | ||||
| 
 | ||||
| .grid-span-9 { | ||||
|   grid-column-end: span 9; | ||||
| } | ||||
| 
 | ||||
| .grid-span-10 { | ||||
|   grid-column-end: span 10; | ||||
| } | ||||
| 
 | ||||
| .grid-span-11 { | ||||
|   grid-column-end: span 11; | ||||
| } | ||||
| 
 | ||||
| .grid-span-12 { | ||||
|   grid-column-end: span 12; | ||||
| } | ||||
| 
 | ||||
| .flex-group-center, | ||||
| .flex-group-left, | ||||
| .flex-group-right { | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   text-align: center; | ||||
| } | ||||
| 
 | ||||
| .flex-group-left { | ||||
|   justify-content: flex-start; | ||||
|   text-align: left; | ||||
| } | ||||
| 
 | ||||
| .flex-group-right { | ||||
|   justify-content: flex-end; | ||||
|   text-align: right; | ||||
| } | ||||
| 
 | ||||
| .flexshrink { | ||||
|   flex: 0; | ||||
| } | ||||
| 
 | ||||
| .flex-between { | ||||
|   justify-content: space-between; | ||||
| } | ||||
| 
 | ||||
| .flexlarge { | ||||
|   flex: 2; | ||||
| } | ||||
| 
 | ||||
| .align-left { | ||||
|   justify-content: flex-start; | ||||
|   text-align: left; | ||||
| } | ||||
| 
 | ||||
| .align-right { | ||||
|   justify-content: flex-end; | ||||
|   text-align: right; | ||||
| } | ||||
| 
 | ||||
| .align-center { | ||||
|   justify-content: center; | ||||
|   text-align: center; | ||||
| } | ||||
| 
 | ||||
| /* Styles limited to wanderhome sheets */ | ||||
| .wanderhome .item-form { | ||||
|   font-family: "Roboto", sans-serif; | ||||
| } | ||||
| .wanderhome .sheet-header { | ||||
|   flex: 0 auto; | ||||
|   overflow: hidden; | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
|   flex-wrap: wrap; | ||||
|   justify-content: flex-start; | ||||
|   margin-bottom: 10px; | ||||
| } | ||||
| .wanderhome .sheet-header .profile-img { | ||||
|   flex: 0 0 100px; | ||||
|   height: 100px; | ||||
|   margin-right: 10px; | ||||
| } | ||||
| .wanderhome .sheet-header .header-fields { | ||||
|   flex: 1; | ||||
| } | ||||
| .wanderhome .sheet-header h1.charname { | ||||
|   height: 50px; | ||||
|   padding: 0px; | ||||
|   margin: 5px 0; | ||||
|   border-bottom: 0; | ||||
| } | ||||
| .wanderhome .sheet-header h1.charname input { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   margin: 0; | ||||
| } | ||||
| .wanderhome .sheet-tabs { | ||||
|   flex: 0; | ||||
| } | ||||
| .wanderhome .sheet-body, | ||||
| .wanderhome .sheet-body .tab, | ||||
| .wanderhome .sheet-body .tab .editor { | ||||
|   height: 100%; | ||||
| } | ||||
| .wanderhome .tox .tox-editor-container { | ||||
|   background: #fff; | ||||
| } | ||||
| .wanderhome .tox .tox-edit-area { | ||||
|   padding: 0 8px; | ||||
| } | ||||
| .wanderhome .resource-label { | ||||
|   font-weight: bold; | ||||
| } | ||||
| .wanderhome .items-header { | ||||
|   height: 28px; | ||||
|   margin: 2px 0; | ||||
|   padding: 0; | ||||
|   align-items: center; | ||||
|   background: rgba(0, 0, 0, 0.05); | ||||
|   border: 2px groove #eeede0; | ||||
|   font-weight: bold; | ||||
| } | ||||
| .wanderhome .items-header > * { | ||||
|   font-size: 14px; | ||||
|   text-align: center; | ||||
| } | ||||
| .wanderhome .items-header .item-name { | ||||
|   font-weight: bold; | ||||
|   padding-left: 5px; | ||||
|   text-align: left; | ||||
|   display: flex; | ||||
| } | ||||
| .wanderhome .items-list { | ||||
|   list-style: none; | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   overflow-y: auto; | ||||
|   scrollbar-width: thin; | ||||
|   color: #444; | ||||
| } | ||||
| .wanderhome .items-list .item-list { | ||||
|   list-style: none; | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
| } | ||||
| .wanderhome .items-list .item-name { | ||||
|   flex: 2; | ||||
|   margin: 0; | ||||
|   overflow: hidden; | ||||
|   font-size: 13px; | ||||
|   text-align: left; | ||||
|   align-items: center; | ||||
|   display: flex; | ||||
| } | ||||
| .wanderhome .items-list .item-name h3, .wanderhome .items-list .item-name h4 { | ||||
|   margin: 0; | ||||
|   white-space: nowrap; | ||||
|   overflow-x: hidden; | ||||
| } | ||||
| .wanderhome .items-list .item-controls { | ||||
|   display: flex; | ||||
|   flex: 0 0 100px; | ||||
|   justify-content: flex-end; | ||||
| } | ||||
| .wanderhome .items-list .item-controls a { | ||||
|   font-size: 12px; | ||||
|   text-align: center; | ||||
|   margin: 0 6px; | ||||
| } | ||||
| .wanderhome .items-list .item { | ||||
|   align-items: center; | ||||
|   padding: 0 2px; | ||||
|   border-bottom: 1px solid #c9c7b8; | ||||
| } | ||||
| .wanderhome .items-list .item:last-child { | ||||
|   border-bottom: none; | ||||
| } | ||||
| .wanderhome .items-list .item .item-name { | ||||
|   color: #191813; | ||||
| } | ||||
| .wanderhome .items-list .item .item-name .item-image { | ||||
|   flex: 0 0 30px; | ||||
|   height: 30px; | ||||
|   background-size: 30px; | ||||
|   border: none; | ||||
|   margin-right: 5px; | ||||
| } | ||||
| .wanderhome .items-list .item-prop { | ||||
|   text-align: center; | ||||
|   border-left: 1px solid #c9c7b8; | ||||
|   border-right: 1px solid #c9c7b8; | ||||
|   font-size: 12px; | ||||
| } | ||||
| .wanderhome .items-list .items-header { | ||||
|   height: 28px; | ||||
|   margin: 2px 0; | ||||
|   padding: 0; | ||||
|   align-items: center; | ||||
|   background: rgba(0, 0, 0, 0.05); | ||||
|   border: 2px groove #eeede0; | ||||
|   font-weight: bold; | ||||
| } | ||||
| .wanderhome .items-list .items-header > * { | ||||
|   font-size: 12px; | ||||
|   text-align: center; | ||||
| } | ||||
| .wanderhome .items-list .items-header .item-name { | ||||
|   padding-left: 5px; | ||||
|   text-align: left; | ||||
| } | ||||
| .wanderhome .item-formula { | ||||
|   flex: 0 0 200px; | ||||
|   padding: 0 8px; | ||||
| } | ||||
| .wanderhome .effects .item .effect-source, | ||||
| .wanderhome .effects .item .effect-duration, | ||||
| .wanderhome .effects .item .effect-controls { | ||||
|   text-align: center; | ||||
|   border-left: 1px solid #c9c7b8; | ||||
|   border-right: 1px solid #c9c7b8; | ||||
|   font-size: 12px; | ||||
| } | ||||
| .wanderhome .effects .item .effect-controls { | ||||
|   border: none; | ||||
| } | ||||
| @ -0,0 +1,41 @@ | ||||
| { | ||||
|   "WANDERHOME": { | ||||
|     "Ability": { | ||||
|       "Str": { "long": "Strength", "abbr": "str" }, | ||||
|       "Con": { "long": "Constitution", "abbr": "con" }, | ||||
|       "Dex": { "long": "Dexterity", "abbr": "dex" }, | ||||
|       "Int": { "long": "Intelligence", "abbr": "int" }, | ||||
|       "Wis": { "long": "Wisdom", "abbr": "wis" }, | ||||
|       "Cha": { "long": "Charisma", "abbr": "cha" } | ||||
|     }, | ||||
|     "SheetLabels": { | ||||
|       "Actor": "Wanderhome Actor Sheet", | ||||
|       "Item": "Wanderhome Item Sheet" | ||||
|     }, | ||||
|     "Item": { | ||||
|       "Spell": { | ||||
|         "SpellLVL": "Level {level} Spells", | ||||
|         "AddLVL": "Add LVL {level}" | ||||
|       } | ||||
|     }, | ||||
|     "Effect": { | ||||
|       "Source": "Source", | ||||
|       "Toggle": "Toggle Effect", | ||||
|       "Temporary": "Temporary Effects", | ||||
|       "Passive": "Passive Effects", | ||||
|       "Inactive": "Inactive Effects" | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   "TYPES": { | ||||
|     "Actor": { | ||||
|       "character": "Character", | ||||
|       "npc": "NPC" | ||||
|     }, | ||||
|     "Item": { | ||||
|       "item": "Item", | ||||
|       "feature": "Feature", | ||||
|       "spell": "Spell" | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| // Export Actors
 | ||||
| export {default as WanderhomeActorBase} from "./base-actor.mjs"; | ||||
| export {default as WanderhomeCharacter} from "./actor-character.mjs"; | ||||
| export {default as WanderhomeNPC} from "./actor-npc.mjs"; | ||||
| 
 | ||||
| // Export Items
 | ||||
| export {default as WanderhomeItemBase} from "./base-item.mjs"; | ||||
| export {default as WanderhomeItem} from "./item-item.mjs"; | ||||
| export {default as WanderhomeFeature} from "./item-feature.mjs"; | ||||
| export {default as WanderhomeSpell} from "./item-spell.mjs"; | ||||
| @ -0,0 +1,52 @@ | ||||
| import WanderhomeActorBase from "./base-actor.mjs"; | ||||
| 
 | ||||
| export default class WanderhomeCharacter extends WanderhomeActorBase { | ||||
| 
 | ||||
|   static defineSchema() { | ||||
|     const fields = foundry.data.fields; | ||||
|     const requiredInteger = { required: true, nullable: false, integer: true }; | ||||
|     const schema = super.defineSchema(); | ||||
| 
 | ||||
|     schema.attributes = new fields.SchemaField({ | ||||
|       level: new fields.SchemaField({ | ||||
|         value: new fields.NumberField({ ...requiredInteger, initial: 1 }) | ||||
|       }), | ||||
|     }); | ||||
| 
 | ||||
|     // Iterate over ability names and create a new SchemaField for each.
 | ||||
|     schema.abilities = new fields.SchemaField(Object.keys(CONFIG.WANDERHOME.abilities).reduce((obj, ability) => { | ||||
|       obj[ability] = new fields.SchemaField({ | ||||
|         value: new fields.NumberField({ ...requiredInteger, initial: 10, min: 0 }), | ||||
|       }); | ||||
|       return obj; | ||||
|     }, {})); | ||||
| 
 | ||||
|     return schema; | ||||
|   } | ||||
| 
 | ||||
|   prepareDerivedData() { | ||||
|     // Loop through ability scores, and add their modifiers to our sheet output.
 | ||||
|     for (const key in this.abilities) { | ||||
|       // Calculate the modifier using d20 rules.
 | ||||
|       this.abilities[key].mod = Math.floor((this.abilities[key].value - 10) / 2); | ||||
|       // Handle ability label localization.
 | ||||
|       this.abilities[key].label = game.i18n.localize(CONFIG.WANDERHOME.abilities[key]) ?? key; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   getRollData() { | ||||
|     const data = {}; | ||||
| 
 | ||||
|     // Copy the ability scores to the top level, so that rolls can use
 | ||||
|     // formulas like `@str.mod + 4`.
 | ||||
|     if (this.abilities) { | ||||
|       for (let [k,v] of Object.entries(this.abilities)) { | ||||
|         data[k] = foundry.utils.deepClone(v); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     data.lvl = this.attributes.level.value; | ||||
| 
 | ||||
|     return data | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,19 @@ | ||||
| import WanderhomeActorBase from "./base-actor.mjs"; | ||||
| 
 | ||||
| export default class WanderhomeNPC extends WanderhomeActorBase { | ||||
| 
 | ||||
|   static defineSchema() { | ||||
|     const fields = foundry.data.fields; | ||||
|     const requiredInteger = { required: true, nullable: false, integer: true }; | ||||
|     const schema = super.defineSchema(); | ||||
| 
 | ||||
|     schema.cr = new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }); | ||||
|     schema.xp = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }); | ||||
|      | ||||
|     return schema | ||||
|   } | ||||
| 
 | ||||
|   prepareDerivedData() { | ||||
|     this.xp = this.cr * this.cr * 100; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,23 @@ | ||||
| import WanderhomeDataModel from "./base-model.mjs"; | ||||
| 
 | ||||
| export default class WanderhomeActorBase extends WanderhomeDataModel { | ||||
| 
 | ||||
|   static defineSchema() { | ||||
|     const fields = foundry.data.fields; | ||||
|     const requiredInteger = { required: true, nullable: false, integer: true }; | ||||
|     const schema = {}; | ||||
| 
 | ||||
|     schema.health = new fields.SchemaField({ | ||||
|       value: new fields.NumberField({ ...requiredInteger, initial: 10, min: 0 }), | ||||
|       max: new fields.NumberField({ ...requiredInteger, initial: 10 }) | ||||
|     }); | ||||
|     schema.power = new fields.SchemaField({ | ||||
|       value: new fields.NumberField({ ...requiredInteger, initial: 5, min: 0 }), | ||||
|       max: new fields.NumberField({ ...requiredInteger, initial: 5 }) | ||||
|     }); | ||||
|     schema.biography = new fields.StringField({ required: true, blank: true }); // equivalent to passing ({initial: ""}) for StringFields
 | ||||
| 
 | ||||
|     return schema; | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,14 @@ | ||||
| import WanderhomeDataModel from "./base-model.mjs"; | ||||
| 
 | ||||
| export default class WanderhomeItemBase extends WanderhomeDataModel { | ||||
| 
 | ||||
|   static defineSchema() { | ||||
|     const fields = foundry.data.fields; | ||||
|     const schema = {}; | ||||
| 
 | ||||
|     schema.description = new fields.StringField({ required: true, blank: true }); | ||||
| 
 | ||||
|     return schema; | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,14 @@ | ||||
| export default class WanderhomeDataModel extends foundry.abstract.TypeDataModel { | ||||
|   /** | ||||
|    * Convert the schema to a plain object. | ||||
|    *  | ||||
|    * The built in `toObject()` method will ignore derived data when using Data Models. | ||||
|    * This additional method will instead use the spread operator to return a simplified | ||||
|    * version of the data. | ||||
|    *  | ||||
|    * @returns {object} Plain object either via deepClone or the spread operator. | ||||
|    */ | ||||
|   toPlainObject() { | ||||
|     return {...this}; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,3 @@ | ||||
| import WanderhomeItemBase from "./base-item.mjs"; | ||||
| 
 | ||||
| export default class WanderhomeFeature extends WanderhomeItemBase {} | ||||
| @ -0,0 +1,31 @@ | ||||
| import WanderhomeItemBase from "./base-item.mjs"; | ||||
| 
 | ||||
| export default class WanderhomeItem extends WanderhomeItemBase { | ||||
| 
 | ||||
|   static defineSchema() { | ||||
|     const fields = foundry.data.fields; | ||||
|     const requiredInteger = { required: true, nullable: false, integer: true }; | ||||
|     const schema = super.defineSchema(); | ||||
| 
 | ||||
|     schema.quantity = new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 }); | ||||
|     schema.weight = new fields.NumberField({ required: true, nullable: false, initial: 0, min: 0 }); | ||||
| 
 | ||||
|     // Break down roll formula into three independent fields
 | ||||
|     schema.roll = new fields.SchemaField({ | ||||
|       diceNum: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 }), | ||||
|       diceSize: new fields.StringField({ initial: "d20" }), | ||||
|       diceBonus: new fields.StringField({ initial: "+@str.mod+ceil(@lvl / 2)" }) | ||||
|     }) | ||||
| 
 | ||||
|     schema.formula = new fields.StringField({ blank: true }); | ||||
| 
 | ||||
|     return schema; | ||||
|   } | ||||
| 
 | ||||
|   prepareDerivedData() { | ||||
|     // Build the formula dynamically using string interpolation
 | ||||
|     const roll = this.roll; | ||||
| 
 | ||||
|     this.formula = `${roll.diceNum}${roll.diceSize}${roll.diceBonus}` | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,13 @@ | ||||
| import WanderhomeItemBase from "./base-item.mjs"; | ||||
| 
 | ||||
| export default class WanderhomeSpell extends WanderhomeItemBase { | ||||
| 
 | ||||
|   static defineSchema() { | ||||
|     const fields = foundry.data.fields; | ||||
|     const schema = super.defineSchema(); | ||||
| 
 | ||||
|     schema.spellLevel = new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1, min: 1, max: 9 }); | ||||
| 
 | ||||
|     return schema; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,69 @@ | ||||
| /** | ||||
|  * Extend the base Actor document by defining a custom roll data structure which is ideal for the Simple system. | ||||
|  * @extends {Actor} | ||||
|  */ | ||||
| export class WanderhomeActor extends Actor { | ||||
|   /** @override */ | ||||
|   prepareData() { | ||||
|     // Prepare data for the actor. Calling the super version of this executes
 | ||||
|     // the following, in order: data reset (to clear active effects),
 | ||||
|     // prepareBaseData(), prepareEmbeddedDocuments() (including active effects),
 | ||||
|     // prepareDerivedData().
 | ||||
|     super.prepareData(); | ||||
|   } | ||||
| 
 | ||||
|   /** @override */ | ||||
|   prepareBaseData() { | ||||
|     // Data modifications in this step occur before processing embedded
 | ||||
|     // documents or derived data.
 | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * @override | ||||
|    * Augment the actor source data with additional dynamic data that isn't  | ||||
|    * handled by the actor's DataModel. Data calculated in this step should be | ||||
|    * available both inside and outside of character sheets (such as if an actor | ||||
|    * is queried and has a roll executed directly from it). | ||||
|    */ | ||||
|   prepareDerivedData() { | ||||
|     const actorData = this; | ||||
|     const flags = actorData.flags.wanderhome || {}; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    *  | ||||
|    * @override | ||||
|    * Augment the actor's default getRollData() method by appending the data object | ||||
|    * generated by the its DataModel's getRollData(), or null. This polymorphic  | ||||
|    * approach is useful when you have actors & items that share a parent Document,  | ||||
|    * but have slightly different data preparation needs. | ||||
|    */ | ||||
|   getRollData() { | ||||
|     return { ...super.getRollData(), ...this.system.getRollData?.() ?? null }; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Convert the actor document to a plain object. | ||||
|    *  | ||||
|    * The built in `toObject()` method will ignore derived data when using Data Models. | ||||
|    * This additional method will instead use the spread operator to return a simplified | ||||
|    * version of the data. | ||||
|    *  | ||||
|    * @returns {object} Plain object either via deepClone or the spread operator. | ||||
|    */ | ||||
|   toPlainObject() { | ||||
|     const result = {...this}; | ||||
| 
 | ||||
|     // Simplify system data.
 | ||||
|     result.system = this.system.toPlainObject(); | ||||
| 
 | ||||
|     // Add items.
 | ||||
|     result.items = this.items?.size > 0 ? this.items.contents : []; | ||||
| 
 | ||||
|     // Add effects.
 | ||||
|     result.effects = this.effects?.size > 0 ? this.effects.contents : []; | ||||
| 
 | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,92 @@ | ||||
| /** | ||||
|  * Extend the basic Item with some very simple modifications. | ||||
|  * @extends {Item} | ||||
|  */ | ||||
| export class WanderhomeItem extends Item { | ||||
|   /** | ||||
|    * Augment the basic Item data model with additional dynamic data. | ||||
|    */ | ||||
|   prepareData() { | ||||
|     // As with the actor class, items are documents that can have their data
 | ||||
|     // preparation methods overridden (such as prepareBaseData()).
 | ||||
|     super.prepareData(); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Prepare a data object which defines the data schema used by dice roll commands against this Item | ||||
|    * @override | ||||
|    */ | ||||
|   getRollData() { | ||||
|     // Starts off by populating the roll data with a shallow copy of `this.system`
 | ||||
|     const rollData = { ...this.system }; | ||||
| 
 | ||||
|     // Quit early if there's no parent actor
 | ||||
|     if (!this.actor) return rollData; | ||||
| 
 | ||||
|     // If present, add the actor's roll data
 | ||||
|     rollData.actor = this.actor.getRollData(); | ||||
| 
 | ||||
|     return rollData; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Convert the actor document to a plain object. | ||||
|    * | ||||
|    * The built in `toObject()` method will ignore derived data when using Data Models. | ||||
|    * This additional method will instead use the spread operator to return a simplified | ||||
|    * version of the data. | ||||
|    * | ||||
|    * @returns {object} Plain object either via deepClone or the spread operator. | ||||
|    */ | ||||
|   toPlainObject() { | ||||
|     const result = { ...this }; | ||||
| 
 | ||||
|     // Simplify system data.
 | ||||
|     result.system = this.system.toPlainObject(); | ||||
| 
 | ||||
|     // Add effects.
 | ||||
|     result.effects = this.effects?.size > 0 ? this.effects.contents : []; | ||||
| 
 | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Handle clickable rolls. | ||||
|    * @param {Event} event   The originating click event | ||||
|    * @private | ||||
|    */ | ||||
|   async roll() { | ||||
|     const item = this; | ||||
| 
 | ||||
|     // Initialize chat data.
 | ||||
|     const speaker = ChatMessage.getSpeaker({ actor: this.actor }); | ||||
|     const rollMode = game.settings.get('core', 'rollMode'); | ||||
|     const label = `[${item.type}] ${item.name}`; | ||||
| 
 | ||||
|     // If there's no roll data, send a chat message.
 | ||||
|     if (!this.system.formula) { | ||||
|       ChatMessage.create({ | ||||
|         speaker: speaker, | ||||
|         rollMode: rollMode, | ||||
|         flavor: label, | ||||
|         content: item.system.description ?? '', | ||||
|       }); | ||||
|     } | ||||
|     // Otherwise, create a roll and send a chat message from it.
 | ||||
|     else { | ||||
|       // Retrieve roll data.
 | ||||
|       const rollData = this.getRollData(); | ||||
| 
 | ||||
|       // Invoke the roll and submit it to chat.
 | ||||
|       const roll = new Roll(rollData.formula, rollData.actor); | ||||
|       // If you need to store the value first, uncomment the next line.
 | ||||
|       // const result = await roll.evaluate();
 | ||||
|       roll.toMessage({ | ||||
|         speaker: speaker, | ||||
|         rollMode: rollMode, | ||||
|         flavor: label, | ||||
|       }); | ||||
|       return roll; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,23 @@ | ||||
| export const WANDERHOME = {}; | ||||
| 
 | ||||
| /** | ||||
|  * The set of Ability Scores used within the system. | ||||
|  * @type {Object} | ||||
|  */ | ||||
| WANDERHOME.abilities = { | ||||
|   str: 'WANDERHOME.Ability.Str.long', | ||||
|   dex: 'WANDERHOME.Ability.Dex.long', | ||||
|   con: 'WANDERHOME.Ability.Con.long', | ||||
|   int: 'WANDERHOME.Ability.Int.long', | ||||
|   wis: 'WANDERHOME.Ability.Wis.long', | ||||
|   cha: 'WANDERHOME.Ability.Cha.long', | ||||
| }; | ||||
| 
 | ||||
| WANDERHOME.abilityAbbreviations = { | ||||
|   str: 'WANDERHOME.Ability.Str.abbr', | ||||
|   dex: 'WANDERHOME.Ability.Dex.abbr', | ||||
|   con: 'WANDERHOME.Ability.Con.abbr', | ||||
|   int: 'WANDERHOME.Ability.Int.abbr', | ||||
|   wis: 'WANDERHOME.Ability.Wis.abbr', | ||||
|   cha: 'WANDERHOME.Ability.Cha.abbr', | ||||
| }; | ||||
| @ -0,0 +1,68 @@ | ||||
| /** | ||||
|  * Manage Active Effect instances through an Actor or Item Sheet via effect control buttons. | ||||
|  * @param {MouseEvent} event      The left-click event on the effect control | ||||
|  * @param {Actor|Item} owner      The owning document which manages this effect | ||||
|  */ | ||||
| export function onManageActiveEffect(event, owner) { | ||||
|   event.preventDefault(); | ||||
|   const a = event.currentTarget; | ||||
|   const li = a.closest('li'); | ||||
|   const effect = li.dataset.effectId | ||||
|     ? owner.effects.get(li.dataset.effectId) | ||||
|     : null; | ||||
|   switch (a.dataset.action) { | ||||
|     case 'create': | ||||
|       return owner.createEmbeddedDocuments('ActiveEffect', [ | ||||
|         { | ||||
|           name: game.i18n.format('DOCUMENT.New', { | ||||
|             type: game.i18n.localize('DOCUMENT.ActiveEffect'), | ||||
|           }), | ||||
|           icon: 'icons/svg/aura.svg', | ||||
|           origin: owner.uuid, | ||||
|           'duration.rounds': | ||||
|             li.dataset.effectType === 'temporary' ? 1 : undefined, | ||||
|           disabled: li.dataset.effectType === 'inactive', | ||||
|         }, | ||||
|       ]); | ||||
|     case 'edit': | ||||
|       return effect.sheet.render(true); | ||||
|     case 'delete': | ||||
|       return effect.delete(); | ||||
|     case 'toggle': | ||||
|       return effect.update({ disabled: !effect.disabled }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Prepare the data structure for Active Effects which are currently embedded in an Actor or Item. | ||||
|  * @param {ActiveEffect[]} effects    A collection or generator of Active Effect documents to prepare sheet data for | ||||
|  * @return {object}                   Data for rendering | ||||
|  */ | ||||
| export function prepareActiveEffectCategories(effects) { | ||||
|   // Define effect header categories
 | ||||
|   const categories = { | ||||
|     temporary: { | ||||
|       type: 'temporary', | ||||
|       label: game.i18n.localize('WANDERHOME.Effect.Temporary'), | ||||
|       effects: [], | ||||
|     }, | ||||
|     passive: { | ||||
|       type: 'passive', | ||||
|       label: game.i18n.localize('WANDERHOME.Effect.Passive'), | ||||
|       effects: [], | ||||
|     }, | ||||
|     inactive: { | ||||
|       type: 'inactive', | ||||
|       label: game.i18n.localize('WANDERHOME.Effect.Inactive'), | ||||
|       effects: [], | ||||
|     }, | ||||
|   }; | ||||
| 
 | ||||
|   // Iterate over active effects, classifying them into categories
 | ||||
|   for (let e of effects) { | ||||
|     if (e.disabled) categories.inactive.effects.push(e); | ||||
|     else if (e.isTemporary) categories.temporary.effects.push(e); | ||||
|     else categories.passive.effects.push(e); | ||||
|   } | ||||
|   return categories; | ||||
| } | ||||
| @ -0,0 +1,16 @@ | ||||
| /** | ||||
|  * Define a set of template paths to pre-load | ||||
|  * Pre-loaded templates are compiled and cached for fast access when rendering | ||||
|  * @return {Promise} | ||||
|  */ | ||||
| export const preloadHandlebarsTemplates = async function () { | ||||
|   return loadTemplates([ | ||||
|     // Actor partials.
 | ||||
|     'systems/wanderhome/templates/actor/parts/actor-features.hbs', | ||||
|     'systems/wanderhome/templates/actor/parts/actor-items.hbs', | ||||
|     'systems/wanderhome/templates/actor/parts/actor-spells.hbs', | ||||
|     'systems/wanderhome/templates/actor/parts/actor-effects.hbs', | ||||
|     // Item partials
 | ||||
|     'systems/wanderhome/templates/item/parts/item-effects.hbs', | ||||
|   ]); | ||||
| }; | ||||
| @ -0,0 +1,256 @@ | ||||
| import { | ||||
|   onManageActiveEffect, | ||||
|   prepareActiveEffectCategories, | ||||
| } from '../helpers/effects.mjs'; | ||||
| 
 | ||||
| /** | ||||
|  * Extend the basic ActorSheet with some very simple modifications | ||||
|  * @extends {ActorSheet} | ||||
|  */ | ||||
| export class WanderhomeActorSheet extends ActorSheet { | ||||
|   /** @override */ | ||||
|   static get defaultOptions() { | ||||
|     return foundry.utils.mergeObject(super.defaultOptions, { | ||||
|       classes: ['wanderhome', 'sheet', 'actor'], | ||||
|       width: 600, | ||||
|       height: 600, | ||||
|       tabs: [ | ||||
|         { | ||||
|           navSelector: '.sheet-tabs', | ||||
|           contentSelector: '.sheet-body', | ||||
|           initial: 'features', | ||||
|         }, | ||||
|       ], | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   /** @override */ | ||||
|   get template() { | ||||
|     return `systems/wanderhome/templates/actor/actor-${this.actor.type}-sheet.hbs`; | ||||
|   } | ||||
| 
 | ||||
|   /* -------------------------------------------- */ | ||||
| 
 | ||||
|   /** @override */ | ||||
|   async getData() { | ||||
|     // Retrieve the data structure from the base sheet. You can inspect or log
 | ||||
|     // the context variable to see the structure, but some key properties for
 | ||||
|     // sheets are the actor object, the data object, whether or not it's
 | ||||
|     // editable, the items array, and the effects array.
 | ||||
|     const context = super.getData(); | ||||
| 
 | ||||
|     // Use a safe clone of the actor data for further operations.
 | ||||
|     const actorData = this.document.toPlainObject(); | ||||
| 
 | ||||
|     // Add the actor's data to context.data for easier access, as well as flags.
 | ||||
|     context.system = actorData.system; | ||||
|     context.flags = actorData.flags; | ||||
| 
 | ||||
|     // Adding a pointer to CONFIG.WANDERHOME
 | ||||
|     context.config = CONFIG.WANDERHOME; | ||||
| 
 | ||||
|     // Prepare character data and items.
 | ||||
|     if (actorData.type == 'character') { | ||||
|       this._prepareItems(context); | ||||
|       this._prepareCharacterData(context); | ||||
|     } | ||||
| 
 | ||||
|     // Prepare NPC data and items.
 | ||||
|     if (actorData.type == 'npc') { | ||||
|       this._prepareItems(context); | ||||
|     } | ||||
| 
 | ||||
|     // Enrich biography info for display
 | ||||
|     // Enrichment turns text like `[[/r 1d20]]` into buttons
 | ||||
|     context.enrichedBiography = await TextEditor.enrichHTML( | ||||
|       this.actor.system.biography, | ||||
|       { | ||||
|         // Whether to show secret blocks in the finished html
 | ||||
|         secrets: this.document.isOwner, | ||||
|         // Necessary in v11, can be removed in v12
 | ||||
|         async: true, | ||||
|         // Data to fill in for inline rolls
 | ||||
|         rollData: this.actor.getRollData(), | ||||
|         // Relative UUID resolution
 | ||||
|         relativeTo: this.actor, | ||||
|       } | ||||
|     ); | ||||
| 
 | ||||
|     // Prepare active effects
 | ||||
|     context.effects = prepareActiveEffectCategories( | ||||
|       // A generator that returns all effects stored on the actor
 | ||||
|       // as well as any items
 | ||||
|       this.actor.allApplicableEffects() | ||||
|     ); | ||||
| 
 | ||||
|     return context; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Character-specific context modifications | ||||
|    * | ||||
|    * @param {object} context The context object to mutate | ||||
|    */ | ||||
|   _prepareCharacterData(context) { | ||||
|     // This is where you can enrich character-specific editor fields
 | ||||
|     // or setup anything else that's specific to this type
 | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Organize and classify Items for Actor sheets. | ||||
|    * | ||||
|    * @param {object} context The context object to mutate | ||||
|    */ | ||||
|   _prepareItems(context) { | ||||
|     // Initialize containers.
 | ||||
|     const gear = []; | ||||
|     const features = []; | ||||
|     const spells = { | ||||
|       0: [], | ||||
|       1: [], | ||||
|       2: [], | ||||
|       3: [], | ||||
|       4: [], | ||||
|       5: [], | ||||
|       6: [], | ||||
|       7: [], | ||||
|       8: [], | ||||
|       9: [], | ||||
|     }; | ||||
| 
 | ||||
|     // Iterate through items, allocating to containers
 | ||||
|     for (let i of context.items) { | ||||
|       i.img = i.img || Item.DEFAULT_ICON; | ||||
|       // Append to gear.
 | ||||
|       if (i.type === 'item') { | ||||
|         gear.push(i); | ||||
|       } | ||||
|       // Append to features.
 | ||||
|       else if (i.type === 'feature') { | ||||
|         features.push(i); | ||||
|       } | ||||
|       // Append to spells.
 | ||||
|       else if (i.type === 'spell') { | ||||
|         if (i.system.spellLevel != undefined) { | ||||
|           spells[i.system.spellLevel].push(i); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // Assign and return
 | ||||
|     context.gear = gear; | ||||
|     context.features = features; | ||||
|     context.spells = spells; | ||||
|   } | ||||
| 
 | ||||
|   /* -------------------------------------------- */ | ||||
| 
 | ||||
|   /** @override */ | ||||
|   activateListeners(html) { | ||||
|     super.activateListeners(html); | ||||
| 
 | ||||
|     // Render the item sheet for viewing/editing prior to the editable check.
 | ||||
|     html.on('click', '.item-edit', (ev) => { | ||||
|       const li = $(ev.currentTarget).parents('.item'); | ||||
|       const item = this.actor.items.get(li.data('itemId')); | ||||
|       item.sheet.render(true); | ||||
|     }); | ||||
| 
 | ||||
|     // -------------------------------------------------------------
 | ||||
|     // Everything below here is only needed if the sheet is editable
 | ||||
|     if (!this.isEditable) return; | ||||
| 
 | ||||
|     // Add Inventory Item
 | ||||
|     html.on('click', '.item-create', this._onItemCreate.bind(this)); | ||||
| 
 | ||||
|     // Delete Inventory Item
 | ||||
|     html.on('click', '.item-delete', (ev) => { | ||||
|       const li = $(ev.currentTarget).parents('.item'); | ||||
|       const item = this.actor.items.get(li.data('itemId')); | ||||
|       item.delete(); | ||||
|       li.slideUp(200, () => this.render(false)); | ||||
|     }); | ||||
| 
 | ||||
|     // Active Effect management
 | ||||
|     html.on('click', '.effect-control', (ev) => { | ||||
|       const row = ev.currentTarget.closest('li'); | ||||
|       const document = | ||||
|         row.dataset.parentId === this.actor.id | ||||
|           ? this.actor | ||||
|           : this.actor.items.get(row.dataset.parentId); | ||||
|       onManageActiveEffect(ev, document); | ||||
|     }); | ||||
| 
 | ||||
|     // Rollable abilities.
 | ||||
|     html.on('click', '.rollable', this._onRoll.bind(this)); | ||||
| 
 | ||||
|     // Drag events for macros.
 | ||||
|     if (this.actor.isOwner) { | ||||
|       let handler = (ev) => this._onDragStart(ev); | ||||
|       html.find('li.item').each((i, li) => { | ||||
|         if (li.classList.contains('inventory-header')) return; | ||||
|         li.setAttribute('draggable', true); | ||||
|         li.addEventListener('dragstart', handler, false); | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset | ||||
|    * @param {Event} event   The originating click event | ||||
|    * @private | ||||
|    */ | ||||
|   async _onItemCreate(event) { | ||||
|     event.preventDefault(); | ||||
|     const header = event.currentTarget; | ||||
|     // Get the type of item to create.
 | ||||
|     const type = header.dataset.type; | ||||
|     // Grab any data associated with this control.
 | ||||
|     const data = duplicate(header.dataset); | ||||
|     // Initialize a default name.
 | ||||
|     const name = `New ${type.capitalize()}`; | ||||
|     // Prepare the item object.
 | ||||
|     const itemData = { | ||||
|       name: name, | ||||
|       type: type, | ||||
|       system: data, | ||||
|     }; | ||||
|     // Remove the type from the dataset since it's in the itemData.type prop.
 | ||||
|     delete itemData.system['type']; | ||||
| 
 | ||||
|     // Finally, create the item!
 | ||||
|     return await Item.create(itemData, { parent: this.actor }); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Handle clickable rolls. | ||||
|    * @param {Event} event   The originating click event | ||||
|    * @private | ||||
|    */ | ||||
|   _onRoll(event) { | ||||
|     event.preventDefault(); | ||||
|     const element = event.currentTarget; | ||||
|     const dataset = element.dataset; | ||||
| 
 | ||||
|     // Handle item rolls.
 | ||||
|     if (dataset.rollType) { | ||||
|       if (dataset.rollType == 'item') { | ||||
|         const itemId = element.closest('.item').dataset.itemId; | ||||
|         const item = this.actor.items.get(itemId); | ||||
|         if (item) return item.roll(); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // Handle rolls that supply the formula directly.
 | ||||
|     if (dataset.roll) { | ||||
|       let label = dataset.label ? `[ability] ${dataset.label}` : ''; | ||||
|       let roll = new Roll(dataset.roll, this.actor.getRollData()); | ||||
|       roll.toMessage({ | ||||
|         speaker: ChatMessage.getSpeaker({ actor: this.actor }), | ||||
|         flavor: label, | ||||
|         rollMode: game.settings.get('core', 'rollMode'), | ||||
|       }); | ||||
|       return roll; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,93 @@ | ||||
| import { | ||||
|   onManageActiveEffect, | ||||
|   prepareActiveEffectCategories, | ||||
| } from '../helpers/effects.mjs'; | ||||
| 
 | ||||
| /** | ||||
|  * Extend the basic ItemSheet with some very simple modifications | ||||
|  * @extends {ItemSheet} | ||||
|  */ | ||||
| export class WanderhomeItemSheet extends ItemSheet { | ||||
|   /** @override */ | ||||
|   static get defaultOptions() { | ||||
|     return foundry.utils.mergeObject(super.defaultOptions, { | ||||
|       classes: ['wanderhome', 'sheet', 'item'], | ||||
|       width: 520, | ||||
|       height: 480, | ||||
|       tabs: [ | ||||
|         { | ||||
|           navSelector: '.sheet-tabs', | ||||
|           contentSelector: '.sheet-body', | ||||
|           initial: 'description', | ||||
|         }, | ||||
|       ], | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   /** @override */ | ||||
|   get template() { | ||||
|     const path = 'systems/wanderhome/templates/item'; | ||||
|     // Return a single sheet for all item types.
 | ||||
|     // return `${path}/item-sheet.hbs`;
 | ||||
| 
 | ||||
|     // Alternatively, you could use the following return statement to do a
 | ||||
|     // unique item sheet by type, like `weapon-sheet.hbs`.
 | ||||
|     return `${path}/item-${this.item.type}-sheet.hbs`; | ||||
|   } | ||||
| 
 | ||||
|   /* -------------------------------------------- */ | ||||
| 
 | ||||
|   /** @override */ | ||||
|   async getData() { | ||||
|     // Retrieve base data structure.
 | ||||
|     const context = super.getData(); | ||||
| 
 | ||||
|     // Use a safe clone of the item data for further operations.
 | ||||
|     const itemData = this.document.toPlainObject(); | ||||
| 
 | ||||
|     // Enrich description info for display
 | ||||
|     // Enrichment turns text like `[[/r 1d20]]` into buttons
 | ||||
|     context.enrichedDescription = await TextEditor.enrichHTML( | ||||
|       this.item.system.description, | ||||
|       { | ||||
|         // Whether to show secret blocks in the finished html
 | ||||
|         secrets: this.document.isOwner, | ||||
|         // Necessary in v11, can be removed in v12
 | ||||
|         async: true, | ||||
|         // Data to fill in for inline rolls
 | ||||
|         rollData: this.item.getRollData(), | ||||
|         // Relative UUID resolution
 | ||||
|         relativeTo: this.item, | ||||
|       } | ||||
|     ); | ||||
| 
 | ||||
|     // Add the item's data to context.data for easier access, as well as flags.
 | ||||
|     context.system = itemData.system; | ||||
|     context.flags = itemData.flags; | ||||
| 
 | ||||
|     // Adding a pointer to CONFIG.WANDERHOME
 | ||||
|     context.config = CONFIG.WANDERHOME; | ||||
| 
 | ||||
|     // Prepare active effects for easier access
 | ||||
|     context.effects = prepareActiveEffectCategories(this.item.effects); | ||||
| 
 | ||||
|     return context; | ||||
|   } | ||||
| 
 | ||||
|   /* -------------------------------------------- */ | ||||
| 
 | ||||
|   /** @override */ | ||||
|   activateListeners(html) { | ||||
|     super.activateListeners(html); | ||||
| 
 | ||||
|     // Everything below here is only needed if the sheet is editable
 | ||||
|     if (!this.isEditable) return; | ||||
| 
 | ||||
|     // Roll handlers, click handlers, etc. would go here.
 | ||||
| 
 | ||||
|     // Active Effect management
 | ||||
|     html.on('click', '.effect-control', (ev) => | ||||
|       onManageActiveEffect(ev, this.item) | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,158 @@ | ||||
| // Import document classes.
 | ||||
| import { WanderhomeActor } from './documents/actor.mjs'; | ||||
| import { WanderhomeItem } from './documents/item.mjs'; | ||||
| // Import sheet classes.
 | ||||
| import { WanderhomeActorSheet } from './sheets/actor-sheet.mjs'; | ||||
| import { WanderhomeItemSheet } from './sheets/item-sheet.mjs'; | ||||
| // Import helper/utility classes and constants.
 | ||||
| import { preloadHandlebarsTemplates } from './helpers/templates.mjs'; | ||||
| import { WANDERHOME } from './helpers/config.mjs'; | ||||
| // Import DataModel classes
 | ||||
| import * as models from './data/_module.mjs'; | ||||
| 
 | ||||
| /* -------------------------------------------- */ | ||||
| /*  Init Hook                                   */ | ||||
| /* -------------------------------------------- */ | ||||
| 
 | ||||
| Hooks.once('init', function () { | ||||
|   // Add utility classes to the global game object so that they're more easily
 | ||||
|   // accessible in global contexts.
 | ||||
|   game.wanderhome = { | ||||
|     WanderhomeActor, | ||||
|     WanderhomeItem, | ||||
|     rollItemMacro, | ||||
|   }; | ||||
| 
 | ||||
|   // Add custom constants for configuration.
 | ||||
|   CONFIG.WANDERHOME = WANDERHOME; | ||||
| 
 | ||||
|   /** | ||||
|    * Set an initiative formula for the system | ||||
|    * @type {String} | ||||
|    */ | ||||
|   CONFIG.Combat.initiative = { | ||||
|     formula: '1d20 + @abilities.dex.mod', | ||||
|     decimals: 2, | ||||
|   }; | ||||
| 
 | ||||
|   // Define custom Document and DataModel classes
 | ||||
|   CONFIG.Actor.documentClass = WanderhomeActor; | ||||
| 
 | ||||
|   // Note that you don't need to declare a DataModel
 | ||||
|   // for the base actor/item classes - they are included
 | ||||
|   // with the Character/NPC as part of super.defineSchema()
 | ||||
|   CONFIG.Actor.dataModels = { | ||||
|     character: models.WanderhomeCharacter, | ||||
|     npc: models.WanderhomeNPC | ||||
|   } | ||||
|   CONFIG.Item.documentClass = WanderhomeItem; | ||||
|   CONFIG.Item.dataModels = { | ||||
|     item: models.WanderhomeItem, | ||||
|     feature: models.WanderhomeFeature, | ||||
|     spell: models.WanderhomeSpell | ||||
|   } | ||||
| 
 | ||||
|   // Active Effects are never copied to the Actor,
 | ||||
|   // but will still apply to the Actor from within the Item
 | ||||
|   // if the transfer property on the Active Effect is true.
 | ||||
|   CONFIG.ActiveEffect.legacyTransferral = false; | ||||
| 
 | ||||
|   // Register sheet application classes
 | ||||
|   Actors.unregisterSheet('core', ActorSheet); | ||||
|   Actors.registerSheet('wanderhome', WanderhomeActorSheet, { | ||||
|     makeDefault: true, | ||||
|     label: 'WANDERHOME.SheetLabels.Actor', | ||||
|   }); | ||||
|   Items.unregisterSheet('core', ItemSheet); | ||||
|   Items.registerSheet('wanderhome', WanderhomeItemSheet, { | ||||
|     makeDefault: true, | ||||
|     label: 'WANDERHOME.SheetLabels.Item', | ||||
|   }); | ||||
| 
 | ||||
|   // Preload Handlebars templates.
 | ||||
|   return preloadHandlebarsTemplates(); | ||||
| }); | ||||
| 
 | ||||
| /* -------------------------------------------- */ | ||||
| /*  Handlebars Helpers                          */ | ||||
| /* -------------------------------------------- */ | ||||
| 
 | ||||
| // If you need to add Handlebars helpers, here is a useful example:
 | ||||
| Handlebars.registerHelper('toLowerCase', function (str) { | ||||
|   return str.toLowerCase(); | ||||
| }); | ||||
| 
 | ||||
| /* -------------------------------------------- */ | ||||
| /*  Ready Hook                                  */ | ||||
| /* -------------------------------------------- */ | ||||
| 
 | ||||
| Hooks.once('ready', function () { | ||||
|   // Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
 | ||||
|   Hooks.on('hotbarDrop', (bar, data, slot) => createItemMacro(data, slot)); | ||||
| }); | ||||
| 
 | ||||
| /* -------------------------------------------- */ | ||||
| /*  Hotbar Macros                               */ | ||||
| /* -------------------------------------------- */ | ||||
| 
 | ||||
| /** | ||||
|  * Create a Macro from an Item drop. | ||||
|  * Get an existing item macro if one exists, otherwise create a new one. | ||||
|  * @param {Object} data     The dropped data | ||||
|  * @param {number} slot     The hotbar slot to use | ||||
|  * @returns {Promise} | ||||
|  */ | ||||
| async function createItemMacro(data, slot) { | ||||
|   // First, determine if this is a valid owned item.
 | ||||
|   if (data.type !== 'Item') return; | ||||
|   if (!data.uuid.includes('Actor.') && !data.uuid.includes('Token.')) { | ||||
|     return ui.notifications.warn( | ||||
|       'You can only create macro buttons for owned Items' | ||||
|     ); | ||||
|   } | ||||
|   // If it is, retrieve it based on the uuid.
 | ||||
|   const item = await Item.fromDropData(data); | ||||
| 
 | ||||
|   // Create the macro command using the uuid.
 | ||||
|   const command = `game.wanderhome.rollItemMacro("${data.uuid}");`; | ||||
|   let macro = game.macros.find( | ||||
|     (m) => m.name === item.name && m.command === command | ||||
|   ); | ||||
|   if (!macro) { | ||||
|     macro = await Macro.create({ | ||||
|       name: item.name, | ||||
|       type: 'script', | ||||
|       img: item.img, | ||||
|       command: command, | ||||
|       flags: { 'wanderhome.itemMacro': true }, | ||||
|     }); | ||||
|   } | ||||
|   game.user.assignHotbarMacro(macro, slot); | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Create a Macro from an Item drop. | ||||
|  * Get an existing item macro if one exists, otherwise create a new one. | ||||
|  * @param {string} itemUuid | ||||
|  */ | ||||
| function rollItemMacro(itemUuid) { | ||||
|   // Reconstruct the drop data so that we can load the item.
 | ||||
|   const dropData = { | ||||
|     type: 'Item', | ||||
|     uuid: itemUuid, | ||||
|   }; | ||||
|   // Load the item from the uuid.
 | ||||
|   Item.fromDropData(dropData).then((item) => { | ||||
|     // Determine if the item loaded and if it's an owned item.
 | ||||
|     if (!item || !item.parent) { | ||||
|       const itemName = item?.name ?? itemUuid; | ||||
|       return ui.notifications.warn( | ||||
|         `Could not find item ${itemName}. You may need to delete and recreate this macro.` | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     // Trigger the item roll
 | ||||
|     item.roll(); | ||||
|   }); | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| ../detect-libc/bin/detect-libc.js | ||||
| @ -0,0 +1 @@ | ||||
| ../sass/sass.js | ||||
| @ -0,0 +1,804 @@ | ||||
| { | ||||
|   "name": "wanderhome", | ||||
|   "version": "2.0.0", | ||||
|   "lockfileVersion": 3, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "node_modules/@inquirer/checkbox": { | ||||
|       "version": "4.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.2.0.tgz", | ||||
|       "integrity": "sha512-fdSw07FLJEU5vbpOPzXo5c6xmMGDzbZE2+niuDHX5N6mc6V0Ebso/q3xiHra4D73+PMsC8MJmcaZKuAAoaQsSA==", | ||||
|       "dependencies": { | ||||
|         "@inquirer/core": "^10.1.15", | ||||
|         "@inquirer/figures": "^1.0.13", | ||||
|         "@inquirer/type": "^3.0.8", | ||||
|         "ansi-escapes": "^4.3.2", | ||||
|         "yoctocolors-cjs": "^2.1.2" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@types/node": ">=18" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/node": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@inquirer/confirm": { | ||||
|       "version": "5.1.14", | ||||
|       "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", | ||||
|       "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", | ||||
|       "dependencies": { | ||||
|         "@inquirer/core": "^10.1.15", | ||||
|         "@inquirer/type": "^3.0.8" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@types/node": ">=18" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/node": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@inquirer/core": { | ||||
|       "version": "10.1.15", | ||||
|       "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.15.tgz", | ||||
|       "integrity": "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==", | ||||
|       "dependencies": { | ||||
|         "@inquirer/figures": "^1.0.13", | ||||
|         "@inquirer/type": "^3.0.8", | ||||
|         "ansi-escapes": "^4.3.2", | ||||
|         "cli-width": "^4.1.0", | ||||
|         "mute-stream": "^2.0.0", | ||||
|         "signal-exit": "^4.1.0", | ||||
|         "wrap-ansi": "^6.2.0", | ||||
|         "yoctocolors-cjs": "^2.1.2" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@types/node": ">=18" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/node": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@inquirer/editor": { | ||||
|       "version": "4.2.16", | ||||
|       "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.16.tgz", | ||||
|       "integrity": "sha512-iSzLjT4C6YKp2DU0fr8T7a97FnRRxMO6CushJnW5ktxLNM2iNeuyUuUA5255eOLPORoGYCrVnuDOEBdGkHGkpw==", | ||||
|       "dependencies": { | ||||
|         "@inquirer/core": "^10.1.15", | ||||
|         "@inquirer/external-editor": "^1.0.0", | ||||
|         "@inquirer/type": "^3.0.8" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@types/node": ">=18" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/node": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@inquirer/expand": { | ||||
|       "version": "4.0.17", | ||||
|       "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.17.tgz", | ||||
|       "integrity": "sha512-PSqy9VmJx/VbE3CT453yOfNa+PykpKg/0SYP7odez1/NWBGuDXgPhp4AeGYYKjhLn5lUUavVS/JbeYMPdH50Mw==", | ||||
|       "dependencies": { | ||||
|         "@inquirer/core": "^10.1.15", | ||||
|         "@inquirer/type": "^3.0.8", | ||||
|         "yoctocolors-cjs": "^2.1.2" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@types/node": ">=18" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/node": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@inquirer/external-editor": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.0.tgz", | ||||
|       "integrity": "sha512-5v3YXc5ZMfL6OJqXPrX9csb4l7NlQA2doO1yynUjpUChT9hg4JcuBVP0RbsEJ/3SL/sxWEyFjT2W69ZhtoBWqg==", | ||||
|       "dependencies": { | ||||
|         "chardet": "^2.1.0", | ||||
|         "iconv-lite": "^0.6.3" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@types/node": ">=18" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@inquirer/figures": { | ||||
|       "version": "1.0.13", | ||||
|       "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", | ||||
|       "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@inquirer/input": { | ||||
|       "version": "4.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.1.tgz", | ||||
|       "integrity": "sha512-tVC+O1rBl0lJpoUZv4xY+WGWY8V5b0zxU1XDsMsIHYregdh7bN5X5QnIONNBAl0K765FYlAfNHS2Bhn7SSOVow==", | ||||
|       "dependencies": { | ||||
|         "@inquirer/core": "^10.1.15", | ||||
|         "@inquirer/type": "^3.0.8" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@types/node": ">=18" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/node": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@inquirer/number": { | ||||
|       "version": "3.0.17", | ||||
|       "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.17.tgz", | ||||
|       "integrity": "sha512-GcvGHkyIgfZgVnnimURdOueMk0CztycfC8NZTiIY9arIAkeOgt6zG57G+7vC59Jns3UX27LMkPKnKWAOF5xEYg==", | ||||
|       "dependencies": { | ||||
|         "@inquirer/core": "^10.1.15", | ||||
|         "@inquirer/type": "^3.0.8" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@types/node": ">=18" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/node": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@inquirer/password": { | ||||
|       "version": "4.0.17", | ||||
|       "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.17.tgz", | ||||
|       "integrity": "sha512-DJolTnNeZ00E1+1TW+8614F7rOJJCM4y4BAGQ3Gq6kQIG+OJ4zr3GLjIjVVJCbKsk2jmkmv6v2kQuN/vriHdZA==", | ||||
|       "dependencies": { | ||||
|         "@inquirer/core": "^10.1.15", | ||||
|         "@inquirer/type": "^3.0.8", | ||||
|         "ansi-escapes": "^4.3.2" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@types/node": ">=18" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/node": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@inquirer/prompts": { | ||||
|       "version": "7.8.1", | ||||
|       "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.8.1.tgz", | ||||
|       "integrity": "sha512-LpBPeIpyCF1H3C7SK/QxJQG4iV1/SRmJdymfcul8PuwtVhD0JI1CSwqmd83VgRgt1QEsDojQYFSXJSgo81PVMw==", | ||||
|       "dependencies": { | ||||
|         "@inquirer/checkbox": "^4.2.0", | ||||
|         "@inquirer/confirm": "^5.1.14", | ||||
|         "@inquirer/editor": "^4.2.16", | ||||
|         "@inquirer/expand": "^4.0.17", | ||||
|         "@inquirer/input": "^4.2.1", | ||||
|         "@inquirer/number": "^3.0.17", | ||||
|         "@inquirer/password": "^4.0.17", | ||||
|         "@inquirer/rawlist": "^4.1.5", | ||||
|         "@inquirer/search": "^3.1.0", | ||||
|         "@inquirer/select": "^4.3.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@types/node": ">=18" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/node": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@inquirer/rawlist": { | ||||
|       "version": "4.1.5", | ||||
|       "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.5.tgz", | ||||
|       "integrity": "sha512-R5qMyGJqtDdi4Ht521iAkNqyB6p2UPuZUbMifakg1sWtu24gc2Z8CJuw8rP081OckNDMgtDCuLe42Q2Kr3BolA==", | ||||
|       "dependencies": { | ||||
|         "@inquirer/core": "^10.1.15", | ||||
|         "@inquirer/type": "^3.0.8", | ||||
|         "yoctocolors-cjs": "^2.1.2" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@types/node": ">=18" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/node": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@inquirer/search": { | ||||
|       "version": "3.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.1.0.tgz", | ||||
|       "integrity": "sha512-PMk1+O/WBcYJDq2H7foV0aAZSmDdkzZB9Mw2v/DmONRJopwA/128cS9M/TXWLKKdEQKZnKwBzqu2G4x/2Nqx8Q==", | ||||
|       "dependencies": { | ||||
|         "@inquirer/core": "^10.1.15", | ||||
|         "@inquirer/figures": "^1.0.13", | ||||
|         "@inquirer/type": "^3.0.8", | ||||
|         "yoctocolors-cjs": "^2.1.2" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@types/node": ">=18" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/node": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@inquirer/select": { | ||||
|       "version": "4.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.3.1.tgz", | ||||
|       "integrity": "sha512-Gfl/5sqOF5vS/LIrSndFgOh7jgoe0UXEizDqahFRkq5aJBLegZ6WjuMh/hVEJwlFQjyLq1z9fRtvUMkb7jM1LA==", | ||||
|       "dependencies": { | ||||
|         "@inquirer/core": "^10.1.15", | ||||
|         "@inquirer/figures": "^1.0.13", | ||||
|         "@inquirer/type": "^3.0.8", | ||||
|         "ansi-escapes": "^4.3.2", | ||||
|         "yoctocolors-cjs": "^2.1.2" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@types/node": ">=18" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/node": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@inquirer/type": { | ||||
|       "version": "3.0.8", | ||||
|       "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", | ||||
|       "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@types/node": ">=18" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/node": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@parcel/watcher": { | ||||
|       "version": "2.5.1", | ||||
|       "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", | ||||
|       "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", | ||||
|       "dev": true, | ||||
|       "hasInstallScript": true, | ||||
|       "optional": true, | ||||
|       "dependencies": { | ||||
|         "detect-libc": "^1.0.3", | ||||
|         "is-glob": "^4.0.3", | ||||
|         "micromatch": "^4.0.5", | ||||
|         "node-addon-api": "^7.0.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 10.0.0" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "type": "opencollective", | ||||
|         "url": "https://opencollective.com/parcel" | ||||
|       }, | ||||
|       "optionalDependencies": { | ||||
|         "@parcel/watcher-android-arm64": "2.5.1", | ||||
|         "@parcel/watcher-darwin-arm64": "2.5.1", | ||||
|         "@parcel/watcher-darwin-x64": "2.5.1", | ||||
|         "@parcel/watcher-freebsd-x64": "2.5.1", | ||||
|         "@parcel/watcher-linux-arm-glibc": "2.5.1", | ||||
|         "@parcel/watcher-linux-arm-musl": "2.5.1", | ||||
|         "@parcel/watcher-linux-arm64-glibc": "2.5.1", | ||||
|         "@parcel/watcher-linux-arm64-musl": "2.5.1", | ||||
|         "@parcel/watcher-linux-x64-glibc": "2.5.1", | ||||
|         "@parcel/watcher-linux-x64-musl": "2.5.1", | ||||
|         "@parcel/watcher-win32-arm64": "2.5.1", | ||||
|         "@parcel/watcher-win32-ia32": "2.5.1", | ||||
|         "@parcel/watcher-win32-x64": "2.5.1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@parcel/watcher-linux-x64-glibc": { | ||||
|       "version": "2.5.1", | ||||
|       "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", | ||||
|       "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", | ||||
|       "cpu": [ | ||||
|         "x64" | ||||
|       ], | ||||
|       "dev": true, | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "linux" | ||||
|       ], | ||||
|       "engines": { | ||||
|         "node": ">= 10.0.0" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "type": "opencollective", | ||||
|         "url": "https://opencollective.com/parcel" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@parcel/watcher-linux-x64-musl": { | ||||
|       "version": "2.5.1", | ||||
|       "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", | ||||
|       "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", | ||||
|       "cpu": [ | ||||
|         "x64" | ||||
|       ], | ||||
|       "dev": true, | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "linux" | ||||
|       ], | ||||
|       "engines": { | ||||
|         "node": ">= 10.0.0" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "type": "opencollective", | ||||
|         "url": "https://opencollective.com/parcel" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@types/node": { | ||||
|       "version": "24.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz", | ||||
|       "integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==", | ||||
|       "peer": true, | ||||
|       "dependencies": { | ||||
|         "undici-types": "~7.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/ansi-escapes": { | ||||
|       "version": "4.3.2", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", | ||||
|       "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", | ||||
|       "dependencies": { | ||||
|         "type-fest": "^0.21.3" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/sindresorhus" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/ansi-regex": { | ||||
|       "version": "5.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", | ||||
|       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/ansi-styles": { | ||||
|       "version": "4.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", | ||||
|       "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", | ||||
|       "dependencies": { | ||||
|         "color-convert": "^2.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/chalk/ansi-styles?sponsor=1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/braces": { | ||||
|       "version": "3.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", | ||||
|       "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", | ||||
|       "dev": true, | ||||
|       "optional": true, | ||||
|       "dependencies": { | ||||
|         "fill-range": "^7.1.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/chardet": { | ||||
|       "version": "2.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", | ||||
|       "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==" | ||||
|     }, | ||||
|     "node_modules/chokidar": { | ||||
|       "version": "4.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", | ||||
|       "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", | ||||
|       "dev": true, | ||||
|       "dependencies": { | ||||
|         "readdirp": "^4.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 14.16.0" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://paulmillr.com/funding/" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/cli-width": { | ||||
|       "version": "4.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", | ||||
|       "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", | ||||
|       "engines": { | ||||
|         "node": ">= 12" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/color-convert": { | ||||
|       "version": "2.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", | ||||
|       "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", | ||||
|       "dependencies": { | ||||
|         "color-name": "~1.1.4" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=7.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/color-name": { | ||||
|       "version": "1.1.4", | ||||
|       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", | ||||
|       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" | ||||
|     }, | ||||
|     "node_modules/detect-libc": { | ||||
|       "version": "1.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", | ||||
|       "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", | ||||
|       "dev": true, | ||||
|       "optional": true, | ||||
|       "bin": { | ||||
|         "detect-libc": "bin/detect-libc.js" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=0.10" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/emoji-regex": { | ||||
|       "version": "8.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", | ||||
|       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" | ||||
|     }, | ||||
|     "node_modules/fill-range": { | ||||
|       "version": "7.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", | ||||
|       "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", | ||||
|       "dev": true, | ||||
|       "optional": true, | ||||
|       "dependencies": { | ||||
|         "to-regex-range": "^5.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/iconv-lite": { | ||||
|       "version": "0.6.3", | ||||
|       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", | ||||
|       "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", | ||||
|       "dependencies": { | ||||
|         "safer-buffer": ">= 2.1.2 < 3.0.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/immutable": { | ||||
|       "version": "5.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", | ||||
|       "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "node_modules/inquirer": { | ||||
|       "version": "12.9.1", | ||||
|       "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.9.1.tgz", | ||||
|       "integrity": "sha512-G7uXAb9OiLcd+9jmA/7KKrItvFF00kKk/jb6CtG+Tm2zSPWfzzhyJwDhVCy+mBmE32o2zJnB5JnknIIv2Ft+AA==", | ||||
|       "dependencies": { | ||||
|         "@inquirer/core": "^10.1.15", | ||||
|         "@inquirer/prompts": "^7.8.1", | ||||
|         "@inquirer/type": "^3.0.8", | ||||
|         "ansi-escapes": "^4.3.2", | ||||
|         "mute-stream": "^2.0.0", | ||||
|         "run-async": "^4.0.5", | ||||
|         "rxjs": "^7.8.2" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@types/node": ">=18" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/node": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/is-extglob": { | ||||
|       "version": "2.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", | ||||
|       "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", | ||||
|       "dev": true, | ||||
|       "optional": true, | ||||
|       "engines": { | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/is-fullwidth-code-point": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", | ||||
|       "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/is-glob": { | ||||
|       "version": "4.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", | ||||
|       "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", | ||||
|       "dev": true, | ||||
|       "optional": true, | ||||
|       "dependencies": { | ||||
|         "is-extglob": "^2.1.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/is-number": { | ||||
|       "version": "7.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", | ||||
|       "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", | ||||
|       "dev": true, | ||||
|       "optional": true, | ||||
|       "engines": { | ||||
|         "node": ">=0.12.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/micromatch": { | ||||
|       "version": "4.0.8", | ||||
|       "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", | ||||
|       "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", | ||||
|       "dev": true, | ||||
|       "optional": true, | ||||
|       "dependencies": { | ||||
|         "braces": "^3.0.3", | ||||
|         "picomatch": "^2.3.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8.6" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/mute-stream": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", | ||||
|       "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", | ||||
|       "engines": { | ||||
|         "node": "^18.17.0 || >=20.5.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/node-addon-api": { | ||||
|       "version": "7.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", | ||||
|       "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", | ||||
|       "dev": true, | ||||
|       "optional": true | ||||
|     }, | ||||
|     "node_modules/picomatch": { | ||||
|       "version": "2.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", | ||||
|       "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", | ||||
|       "dev": true, | ||||
|       "optional": true, | ||||
|       "engines": { | ||||
|         "node": ">=8.6" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/jonschlinkert" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/readdirp": { | ||||
|       "version": "4.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", | ||||
|       "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", | ||||
|       "dev": true, | ||||
|       "engines": { | ||||
|         "node": ">= 14.18.0" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "type": "individual", | ||||
|         "url": "https://paulmillr.com/funding/" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/run-async": { | ||||
|       "version": "4.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.6.tgz", | ||||
|       "integrity": "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==", | ||||
|       "engines": { | ||||
|         "node": ">=0.12.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/rxjs": { | ||||
|       "version": "7.8.2", | ||||
|       "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", | ||||
|       "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", | ||||
|       "dependencies": { | ||||
|         "tslib": "^2.1.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/safer-buffer": { | ||||
|       "version": "2.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", | ||||
|       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" | ||||
|     }, | ||||
|     "node_modules/sass": { | ||||
|       "version": "1.90.0", | ||||
|       "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", | ||||
|       "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", | ||||
|       "dev": true, | ||||
|       "dependencies": { | ||||
|         "chokidar": "^4.0.0", | ||||
|         "immutable": "^5.0.2", | ||||
|         "source-map-js": ">=0.6.2 <2.0.0" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "sass": "sass.js" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=14.0.0" | ||||
|       }, | ||||
|       "optionalDependencies": { | ||||
|         "@parcel/watcher": "^2.4.1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/signal-exit": { | ||||
|       "version": "4.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", | ||||
|       "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", | ||||
|       "engines": { | ||||
|         "node": ">=14" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/isaacs" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/source-map-js": { | ||||
|       "version": "1.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", | ||||
|       "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", | ||||
|       "dev": true, | ||||
|       "engines": { | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/string-width": { | ||||
|       "version": "4.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", | ||||
|       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", | ||||
|       "dependencies": { | ||||
|         "emoji-regex": "^8.0.0", | ||||
|         "is-fullwidth-code-point": "^3.0.0", | ||||
|         "strip-ansi": "^6.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/strip-ansi": { | ||||
|       "version": "6.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", | ||||
|       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", | ||||
|       "dependencies": { | ||||
|         "ansi-regex": "^5.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/to-regex-range": { | ||||
|       "version": "5.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", | ||||
|       "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", | ||||
|       "dev": true, | ||||
|       "optional": true, | ||||
|       "dependencies": { | ||||
|         "is-number": "^7.0.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/tslib": { | ||||
|       "version": "2.8.1", | ||||
|       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", | ||||
|       "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" | ||||
|     }, | ||||
|     "node_modules/type-fest": { | ||||
|       "version": "0.21.3", | ||||
|       "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", | ||||
|       "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", | ||||
|       "engines": { | ||||
|         "node": ">=10" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/sindresorhus" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/undici-types": { | ||||
|       "version": "7.10.0", | ||||
|       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", | ||||
|       "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", | ||||
|       "peer": true | ||||
|     }, | ||||
|     "node_modules/wrap-ansi": { | ||||
|       "version": "6.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", | ||||
|       "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", | ||||
|       "dependencies": { | ||||
|         "ansi-styles": "^4.0.0", | ||||
|         "string-width": "^4.1.0", | ||||
|         "strip-ansi": "^6.0.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/yoctocolors-cjs": { | ||||
|       "version": "2.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", | ||||
|       "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/sindresorhus" | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,22 @@ | ||||
| Copyright (c) 2025 Simon Boudrias | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person | ||||
| obtaining a copy of this software and associated documentation | ||||
| files (the "Software"), to deal in the Software without | ||||
| restriction, including without limitation the rights to use, | ||||
| copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the | ||||
| Software is furnished to do so, subject to the following | ||||
| conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be | ||||
| included in all copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | ||||
| OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | ||||
| HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||||
| WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
| FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | ||||
| OTHER DEALINGS IN THE SOFTWARE. | ||||
| @ -0,0 +1,182 @@ | ||||
| # `@inquirer/checkbox` | ||||
| 
 | ||||
| Simple interactive command line prompt to display a list of checkboxes (multi select). | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| # Special Thanks | ||||
| 
 | ||||
| <div align="center" markdown="1"> | ||||
| 
 | ||||
| [](https://graphite.dev/?utm_source=npmjs&utm_medium=repo&utm_campaign=inquirerjs)<br> | ||||
| 
 | ||||
| ### [Graphite is the AI developer productivity platform helping teams on GitHub ship higher quality software, faster](https://graphite.dev/?utm_source=npmjs&utm_medium=repo&utm_campaign=inquirerjs) | ||||
| 
 | ||||
| </div> | ||||
| 
 | ||||
| # Installation | ||||
| 
 | ||||
| <table> | ||||
| <tr> | ||||
|   <th>npm</th> | ||||
|   <th>yarn</th> | ||||
| </tr> | ||||
| <tr> | ||||
| <td> | ||||
| 
 | ||||
| ```sh | ||||
| npm install @inquirer/prompts | ||||
| ``` | ||||
| 
 | ||||
| </td> | ||||
| <td> | ||||
| 
 | ||||
| ```sh | ||||
| yarn add @inquirer/prompts | ||||
| ``` | ||||
| 
 | ||||
| </td> | ||||
| </tr> | ||||
| <tr> | ||||
| <td colSpan="2" align="center">Or</td> | ||||
| </tr> | ||||
| <tr> | ||||
| <td> | ||||
| 
 | ||||
| ```sh | ||||
| npm install @inquirer/checkbox | ||||
| ``` | ||||
| 
 | ||||
| </td> | ||||
| <td> | ||||
| 
 | ||||
| ```sh | ||||
| yarn add @inquirer/checkbox | ||||
| ``` | ||||
| 
 | ||||
| </td> | ||||
| </tr> | ||||
| </table> | ||||
| 
 | ||||
| # Usage | ||||
| 
 | ||||
| ```js | ||||
| import { checkbox, Separator } from '@inquirer/prompts'; | ||||
| // Or | ||||
| // import checkbox, { Separator } from '@inquirer/checkbox'; | ||||
| 
 | ||||
| const answer = await checkbox({ | ||||
|   message: 'Select a package manager', | ||||
|   choices: [ | ||||
|     { name: 'npm', value: 'npm' }, | ||||
|     { name: 'yarn', value: 'yarn' }, | ||||
|     new Separator(), | ||||
|     { name: 'pnpm', value: 'pnpm', disabled: true }, | ||||
|     { | ||||
|       name: 'pnpm', | ||||
|       value: 'pnpm', | ||||
|       disabled: '(pnpm is not available)', | ||||
|     }, | ||||
|   ], | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| ## Options | ||||
| 
 | ||||
| | Property  | Type                                    | Required | Description                                                                                                                                                                                           | | ||||
| | --------- | --------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||||
| | message   | `string`                                | yes      | The question to ask                                                                                                                                                                                   | | ||||
| | choices   | `Choice[]`                              | yes      | List of the available choices.                                                                                                                                                                        | | ||||
| | pageSize  | `number`                                | no       | By default, lists of choice longer than 7 will be paginated. Use this option to control how many choices will appear on the screen at once.                                                           | | ||||
| | loop      | `boolean`                               | no       | Defaults to `true`. When set to `false`, the cursor will be constrained to the top and bottom of the choice list without looping.                                                                     | | ||||
| | required  | `boolean`                               | no       | When set to `true`, ensures at least one choice must be selected.                                                                                                                                     | | ||||
| | validate  | `async (Choice[]) => boolean \| string` | no       | On submit, validate the choices. When returning a string, it'll be used as the error message displayed to the user. Note: returning a rejected promise, we'll assume a code error happened and crash. | | ||||
| | shortcuts | [See Shortcuts](#Shortcuts)             | no       | Customize shortcut keys for `all` and `invert`.                                                                                                                                                       | | ||||
| | theme     | [See Theming](#Theming)                 | no       | Customize look of the prompt.                                                                                                                                                                         | | ||||
| 
 | ||||
| `Separator` objects can be used in the `choices` array to render non-selectable lines in the choice list. By default it'll render a line, but you can provide the text as argument (`new Separator('-- Dependencies --')`). This option is often used to add labels to groups within long list of options. | ||||
| 
 | ||||
| ### `Choice` object | ||||
| 
 | ||||
| The `Choice` object is typed as | ||||
| 
 | ||||
| ```ts | ||||
| type Choice<Value> = { | ||||
|   value: Value; | ||||
|   name?: string; | ||||
|   description?: string; | ||||
|   short?: string; | ||||
|   checked?: boolean; | ||||
|   disabled?: boolean | string; | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
| Here's each property: | ||||
| 
 | ||||
| - `value`: The value is what will be returned by `await checkbox()`. | ||||
| - `name`: This is the string displayed in the choice list. | ||||
| - `description`: Option for a longer description string that'll appear under the list when the cursor highlight a given choice. | ||||
| - `short`: Once the prompt is done (press enter), we'll use `short` if defined to render next to the question. By default we'll use `name`. | ||||
| - `checked`: If `true`, the option will be checked by default. | ||||
| - `disabled`: Disallow the option from being selected. If `disabled` is a string, it'll be used as a help tip explaining why the choice isn't available. | ||||
| 
 | ||||
| Also note the `choices` array can contain `Separator`s to help organize long lists. | ||||
| 
 | ||||
| `choices` can also be an array of string, in which case the string will be used both as the `value` and the `name`. | ||||
| 
 | ||||
| ## Shortcuts | ||||
| 
 | ||||
| You can customize the shortcut keys for `all` and `invert` or disable them by setting them to `null`. | ||||
| 
 | ||||
| ```ts | ||||
| type Shortcuts = { | ||||
|   all?: string | null; // default: 'a' | ||||
|   invert?: string | null; // default: 'i' | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
| ## Theming | ||||
| 
 | ||||
| You can theme a prompt by passing a `theme` object option. The theme object only need to includes the keys you wish to modify, we'll fallback on the defaults for the rest. | ||||
| 
 | ||||
| ```ts | ||||
| type Theme = { | ||||
|   prefix: string | { idle: string; done: string }; | ||||
|   spinner: { | ||||
|     interval: number; | ||||
|     frames: string[]; | ||||
|   }; | ||||
|   style: { | ||||
|     answer: (text: string) => string; | ||||
|     message: (text: string, status: 'idle' | 'done' | 'loading') => string; | ||||
|     error: (text: string) => string; | ||||
|     defaultAnswer: (text: string) => string; | ||||
|     help: (text: string) => string; | ||||
|     highlight: (text: string) => string; | ||||
|     key: (text: string) => string; | ||||
|     disabledChoice: (text: string) => string; | ||||
|     description: (text: string) => string; | ||||
|     renderSelectedChoices: <T>( | ||||
|       selectedChoices: ReadonlyArray<Choice<T>>, | ||||
|       allChoices: ReadonlyArray<Choice<T> | Separator>, | ||||
|     ) => string; | ||||
|   }; | ||||
|   icon: { | ||||
|     checked: string; | ||||
|     unchecked: string; | ||||
|     cursor: string; | ||||
|   }; | ||||
|   helpMode: 'always' | 'never' | 'auto'; | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
| ### `theme.helpMode` | ||||
| 
 | ||||
| - `auto` (default): Hide the help tips after an interaction occurs. The scroll tip will hide after any interactions, the selection tip will hide as soon as a first selection is done. | ||||
| - `always`: The help tips will always show and never hide. | ||||
| - `never`: The help tips will never show. | ||||
| 
 | ||||
| # License | ||||
| 
 | ||||
| Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/> | ||||
| Licensed under the MIT license. | ||||
| @ -0,0 +1,52 @@ | ||||
| import { Separator, type Theme } from '@inquirer/core'; | ||||
| import type { PartialDeep } from '@inquirer/type'; | ||||
| type CheckboxTheme = { | ||||
|     icon: { | ||||
|         checked: string; | ||||
|         unchecked: string; | ||||
|         cursor: string; | ||||
|     }; | ||||
|     style: { | ||||
|         disabledChoice: (text: string) => string; | ||||
|         renderSelectedChoices: <T>(selectedChoices: ReadonlyArray<NormalizedChoice<T>>, allChoices: ReadonlyArray<NormalizedChoice<T> | Separator>) => string; | ||||
|         description: (text: string) => string; | ||||
|     }; | ||||
|     helpMode: 'always' | 'never' | 'auto'; | ||||
| }; | ||||
| type CheckboxShortcuts = { | ||||
|     all?: string | null; | ||||
|     invert?: string | null; | ||||
| }; | ||||
| type Choice<Value> = { | ||||
|     value: Value; | ||||
|     name?: string; | ||||
|     description?: string; | ||||
|     short?: string; | ||||
|     disabled?: boolean | string; | ||||
|     checked?: boolean; | ||||
|     type?: never; | ||||
| }; | ||||
| type NormalizedChoice<Value> = { | ||||
|     value: Value; | ||||
|     name: string; | ||||
|     description?: string; | ||||
|     short: string; | ||||
|     disabled: boolean | string; | ||||
|     checked: boolean; | ||||
| }; | ||||
| declare const _default: <Value>(config: { | ||||
|     message: string; | ||||
|     prefix?: string | undefined; | ||||
|     pageSize?: number | undefined; | ||||
|     instructions?: string | boolean | undefined; | ||||
|     choices: readonly (string | Separator)[] | readonly (Separator | Choice<Value>)[]; | ||||
|     loop?: boolean | undefined; | ||||
|     required?: boolean | undefined; | ||||
|     validate?: ((choices: readonly Choice<Value>[]) => boolean | string | Promise<string | boolean>) | undefined; | ||||
|     theme?: PartialDeep<Theme<CheckboxTheme>> | undefined; | ||||
|     shortcuts?: CheckboxShortcuts | undefined; | ||||
| }, context?: import("@inquirer/type").Context) => Promise<Value[]> & { | ||||
|     cancel: () => void; | ||||
| }; | ||||
| export default _default; | ||||
| export { Separator } from '@inquirer/core'; | ||||
| @ -0,0 +1,207 @@ | ||||
| "use strict"; | ||||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||||
|     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.Separator = void 0; | ||||
| const core_1 = require("@inquirer/core"); | ||||
| const yoctocolors_cjs_1 = __importDefault(require("yoctocolors-cjs")); | ||||
| const figures_1 = __importDefault(require("@inquirer/figures")); | ||||
| const ansi_escapes_1 = __importDefault(require("ansi-escapes")); | ||||
| const checkboxTheme = { | ||||
|     icon: { | ||||
|         checked: yoctocolors_cjs_1.default.green(figures_1.default.circleFilled), | ||||
|         unchecked: figures_1.default.circle, | ||||
|         cursor: figures_1.default.pointer, | ||||
|     }, | ||||
|     style: { | ||||
|         disabledChoice: (text) => yoctocolors_cjs_1.default.dim(`- ${text}`), | ||||
|         renderSelectedChoices: (selectedChoices) => selectedChoices.map((choice) => choice.short).join(', '), | ||||
|         description: (text) => yoctocolors_cjs_1.default.cyan(text), | ||||
|     }, | ||||
|     helpMode: 'auto', | ||||
| }; | ||||
| function isSelectable(item) { | ||||
|     return !core_1.Separator.isSeparator(item) && !item.disabled; | ||||
| } | ||||
| function isChecked(item) { | ||||
|     return isSelectable(item) && Boolean(item.checked); | ||||
| } | ||||
| function toggle(item) { | ||||
|     return isSelectable(item) ? { ...item, checked: !item.checked } : item; | ||||
| } | ||||
| function check(checked) { | ||||
|     return function (item) { | ||||
|         return isSelectable(item) ? { ...item, checked } : item; | ||||
|     }; | ||||
| } | ||||
| function normalizeChoices(choices) { | ||||
|     return choices.map((choice) => { | ||||
|         if (core_1.Separator.isSeparator(choice)) | ||||
|             return choice; | ||||
|         if (typeof choice === 'string') { | ||||
|             return { | ||||
|                 value: choice, | ||||
|                 name: choice, | ||||
|                 short: choice, | ||||
|                 disabled: false, | ||||
|                 checked: false, | ||||
|             }; | ||||
|         } | ||||
|         const name = choice.name ?? String(choice.value); | ||||
|         const normalizedChoice = { | ||||
|             value: choice.value, | ||||
|             name, | ||||
|             short: choice.short ?? name, | ||||
|             disabled: choice.disabled ?? false, | ||||
|             checked: choice.checked ?? false, | ||||
|         }; | ||||
|         if (choice.description) { | ||||
|             normalizedChoice.description = choice.description; | ||||
|         } | ||||
|         return normalizedChoice; | ||||
|     }); | ||||
| } | ||||
| exports.default = (0, core_1.createPrompt)((config, done) => { | ||||
|     const { instructions, pageSize = 7, loop = true, required, validate = () => true, } = config; | ||||
|     const shortcuts = { all: 'a', invert: 'i', ...config.shortcuts }; | ||||
|     const theme = (0, core_1.makeTheme)(checkboxTheme, config.theme); | ||||
|     const firstRender = (0, core_1.useRef)(true); | ||||
|     const [status, setStatus] = (0, core_1.useState)('idle'); | ||||
|     const prefix = (0, core_1.usePrefix)({ status, theme }); | ||||
|     const [items, setItems] = (0, core_1.useState)(normalizeChoices(config.choices)); | ||||
|     const bounds = (0, core_1.useMemo)(() => { | ||||
|         const first = items.findIndex(isSelectable); | ||||
|         const last = items.findLastIndex(isSelectable); | ||||
|         if (first === -1) { | ||||
|             throw new core_1.ValidationError('[checkbox prompt] No selectable choices. All choices are disabled.'); | ||||
|         } | ||||
|         return { first, last }; | ||||
|     }, [items]); | ||||
|     const [active, setActive] = (0, core_1.useState)(bounds.first); | ||||
|     const [showHelpTip, setShowHelpTip] = (0, core_1.useState)(true); | ||||
|     const [errorMsg, setError] = (0, core_1.useState)(); | ||||
|     (0, core_1.useKeypress)(async (key) => { | ||||
|         if ((0, core_1.isEnterKey)(key)) { | ||||
|             const selection = items.filter(isChecked); | ||||
|             const isValid = await validate([...selection]); | ||||
|             if (required && !items.some(isChecked)) { | ||||
|                 setError('At least one choice must be selected'); | ||||
|             } | ||||
|             else if (isValid === true) { | ||||
|                 setStatus('done'); | ||||
|                 done(selection.map((choice) => choice.value)); | ||||
|             } | ||||
|             else { | ||||
|                 setError(isValid || 'You must select a valid value'); | ||||
|             } | ||||
|         } | ||||
|         else if ((0, core_1.isUpKey)(key) || (0, core_1.isDownKey)(key)) { | ||||
|             if (loop || | ||||
|                 ((0, core_1.isUpKey)(key) && active !== bounds.first) || | ||||
|                 ((0, core_1.isDownKey)(key) && active !== bounds.last)) { | ||||
|                 const offset = (0, core_1.isUpKey)(key) ? -1 : 1; | ||||
|                 let next = active; | ||||
|                 do { | ||||
|                     next = (next + offset + items.length) % items.length; | ||||
|                 } while (!isSelectable(items[next])); | ||||
|                 setActive(next); | ||||
|             } | ||||
|         } | ||||
|         else if ((0, core_1.isSpaceKey)(key)) { | ||||
|             setError(undefined); | ||||
|             setShowHelpTip(false); | ||||
|             setItems(items.map((choice, i) => (i === active ? toggle(choice) : choice))); | ||||
|         } | ||||
|         else if (key.name === shortcuts.all) { | ||||
|             const selectAll = items.some((choice) => isSelectable(choice) && !choice.checked); | ||||
|             setItems(items.map(check(selectAll))); | ||||
|         } | ||||
|         else if (key.name === shortcuts.invert) { | ||||
|             setItems(items.map(toggle)); | ||||
|         } | ||||
|         else if ((0, core_1.isNumberKey)(key)) { | ||||
|             const selectedIndex = Number(key.name) - 1; | ||||
|             // Find the nth item (ignoring separators)
 | ||||
|             let selectableIndex = -1; | ||||
|             const position = items.findIndex((item) => { | ||||
|                 if (core_1.Separator.isSeparator(item)) | ||||
|                     return false; | ||||
|                 selectableIndex++; | ||||
|                 return selectableIndex === selectedIndex; | ||||
|             }); | ||||
|             const selectedItem = items[position]; | ||||
|             if (selectedItem && isSelectable(selectedItem)) { | ||||
|                 setActive(position); | ||||
|                 setItems(items.map((choice, i) => (i === position ? toggle(choice) : choice))); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|     const message = theme.style.message(config.message, status); | ||||
|     let description; | ||||
|     const page = (0, core_1.usePagination)({ | ||||
|         items, | ||||
|         active, | ||||
|         renderItem({ item, isActive }) { | ||||
|             if (core_1.Separator.isSeparator(item)) { | ||||
|                 return ` ${item.separator}`; | ||||
|             } | ||||
|             if (item.disabled) { | ||||
|                 const disabledLabel = typeof item.disabled === 'string' ? item.disabled : '(disabled)'; | ||||
|                 return theme.style.disabledChoice(`${item.name} ${disabledLabel}`); | ||||
|             } | ||||
|             if (isActive) { | ||||
|                 description = item.description; | ||||
|             } | ||||
|             const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked; | ||||
|             const color = isActive ? theme.style.highlight : (x) => x; | ||||
|             const cursor = isActive ? theme.icon.cursor : ' '; | ||||
|             return color(`${cursor}${checkbox} ${item.name}`); | ||||
|         }, | ||||
|         pageSize, | ||||
|         loop, | ||||
|     }); | ||||
|     if (status === 'done') { | ||||
|         const selection = items.filter(isChecked); | ||||
|         const answer = theme.style.answer(theme.style.renderSelectedChoices(selection, items)); | ||||
|         return `${prefix} ${message} ${answer}`; | ||||
|     } | ||||
|     let helpTipTop = ''; | ||||
|     let helpTipBottom = ''; | ||||
|     if (theme.helpMode === 'always' || | ||||
|         (theme.helpMode === 'auto' && | ||||
|             showHelpTip && | ||||
|             (instructions === undefined || instructions))) { | ||||
|         if (typeof instructions === 'string') { | ||||
|             helpTipTop = instructions; | ||||
|         } | ||||
|         else { | ||||
|             const keys = [ | ||||
|                 `${theme.style.key('space')} to select`, | ||||
|                 shortcuts.all ? `${theme.style.key(shortcuts.all)} to toggle all` : '', | ||||
|                 shortcuts.invert | ||||
|                     ? `${theme.style.key(shortcuts.invert)} to invert selection` | ||||
|                     : '', | ||||
|                 `and ${theme.style.key('enter')} to proceed`, | ||||
|             ]; | ||||
|             helpTipTop = ` (Press ${keys.filter((key) => key !== '').join(', ')})`; | ||||
|         } | ||||
|         if (items.length > pageSize && | ||||
|             (theme.helpMode === 'always' || | ||||
|                 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | ||||
|                 (theme.helpMode === 'auto' && firstRender.current))) { | ||||
|             helpTipBottom = `\n${theme.style.help('(Use arrow keys to reveal more choices)')}`; | ||||
|             firstRender.current = false; | ||||
|         } | ||||
|     } | ||||
|     const choiceDescription = description | ||||
|         ? `\n${theme.style.description(description)}` | ||||
|         : ``; | ||||
|     let error = ''; | ||||
|     if (errorMsg) { | ||||
|         error = `\n${theme.style.error(errorMsg)}`; | ||||
|     } | ||||
|     return `${prefix} ${message}${helpTipTop}\n${page}${helpTipBottom}${choiceDescription}${error}${ansi_escapes_1.default.cursorHide}`; | ||||
| }); | ||||
| var core_2 = require("@inquirer/core"); | ||||
| Object.defineProperty(exports, "Separator", { enumerable: true, get: function () { return core_2.Separator; } }); | ||||
| @ -0,0 +1,3 @@ | ||||
| { | ||||
|   "type": "commonjs" | ||||
| } | ||||
| @ -0,0 +1,52 @@ | ||||
| import { Separator, type Theme } from '@inquirer/core'; | ||||
| import type { PartialDeep } from '@inquirer/type'; | ||||
| type CheckboxTheme = { | ||||
|     icon: { | ||||
|         checked: string; | ||||
|         unchecked: string; | ||||
|         cursor: string; | ||||
|     }; | ||||
|     style: { | ||||
|         disabledChoice: (text: string) => string; | ||||
|         renderSelectedChoices: <T>(selectedChoices: ReadonlyArray<NormalizedChoice<T>>, allChoices: ReadonlyArray<NormalizedChoice<T> | Separator>) => string; | ||||
|         description: (text: string) => string; | ||||
|     }; | ||||
|     helpMode: 'always' | 'never' | 'auto'; | ||||
| }; | ||||
| type CheckboxShortcuts = { | ||||
|     all?: string | null; | ||||
|     invert?: string | null; | ||||
| }; | ||||
| type Choice<Value> = { | ||||
|     value: Value; | ||||
|     name?: string; | ||||
|     description?: string; | ||||
|     short?: string; | ||||
|     disabled?: boolean | string; | ||||
|     checked?: boolean; | ||||
|     type?: never; | ||||
| }; | ||||
| type NormalizedChoice<Value> = { | ||||
|     value: Value; | ||||
|     name: string; | ||||
|     description?: string; | ||||
|     short: string; | ||||
|     disabled: boolean | string; | ||||
|     checked: boolean; | ||||
| }; | ||||
| declare const _default: <Value>(config: { | ||||
|     message: string; | ||||
|     prefix?: string | undefined; | ||||
|     pageSize?: number | undefined; | ||||
|     instructions?: string | boolean | undefined; | ||||
|     choices: readonly (string | Separator)[] | readonly (Separator | Choice<Value>)[]; | ||||
|     loop?: boolean | undefined; | ||||
|     required?: boolean | undefined; | ||||
|     validate?: ((choices: readonly Choice<Value>[]) => boolean | string | Promise<string | boolean>) | undefined; | ||||
|     theme?: PartialDeep<Theme<CheckboxTheme>> | undefined; | ||||
|     shortcuts?: CheckboxShortcuts | undefined; | ||||
| }, context?: import("@inquirer/type").Context) => Promise<Value[]> & { | ||||
|     cancel: () => void; | ||||
| }; | ||||
| export default _default; | ||||
| export { Separator } from '@inquirer/core'; | ||||
| @ -0,0 +1,200 @@ | ||||
| import { createPrompt, useState, useKeypress, usePrefix, usePagination, useRef, useMemo, makeTheme, isUpKey, isDownKey, isSpaceKey, isNumberKey, isEnterKey, ValidationError, Separator, } from '@inquirer/core'; | ||||
| import colors from 'yoctocolors-cjs'; | ||||
| import figures from '@inquirer/figures'; | ||||
| import ansiEscapes from 'ansi-escapes'; | ||||
| const checkboxTheme = { | ||||
|     icon: { | ||||
|         checked: colors.green(figures.circleFilled), | ||||
|         unchecked: figures.circle, | ||||
|         cursor: figures.pointer, | ||||
|     }, | ||||
|     style: { | ||||
|         disabledChoice: (text) => colors.dim(`- ${text}`), | ||||
|         renderSelectedChoices: (selectedChoices) => selectedChoices.map((choice) => choice.short).join(', '), | ||||
|         description: (text) => colors.cyan(text), | ||||
|     }, | ||||
|     helpMode: 'auto', | ||||
| }; | ||||
| function isSelectable(item) { | ||||
|     return !Separator.isSeparator(item) && !item.disabled; | ||||
| } | ||||
| function isChecked(item) { | ||||
|     return isSelectable(item) && Boolean(item.checked); | ||||
| } | ||||
| function toggle(item) { | ||||
|     return isSelectable(item) ? { ...item, checked: !item.checked } : item; | ||||
| } | ||||
| function check(checked) { | ||||
|     return function (item) { | ||||
|         return isSelectable(item) ? { ...item, checked } : item; | ||||
|     }; | ||||
| } | ||||
| function normalizeChoices(choices) { | ||||
|     return choices.map((choice) => { | ||||
|         if (Separator.isSeparator(choice)) | ||||
|             return choice; | ||||
|         if (typeof choice === 'string') { | ||||
|             return { | ||||
|                 value: choice, | ||||
|                 name: choice, | ||||
|                 short: choice, | ||||
|                 disabled: false, | ||||
|                 checked: false, | ||||
|             }; | ||||
|         } | ||||
|         const name = choice.name ?? String(choice.value); | ||||
|         const normalizedChoice = { | ||||
|             value: choice.value, | ||||
|             name, | ||||
|             short: choice.short ?? name, | ||||
|             disabled: choice.disabled ?? false, | ||||
|             checked: choice.checked ?? false, | ||||
|         }; | ||||
|         if (choice.description) { | ||||
|             normalizedChoice.description = choice.description; | ||||
|         } | ||||
|         return normalizedChoice; | ||||
|     }); | ||||
| } | ||||
| export default createPrompt((config, done) => { | ||||
|     const { instructions, pageSize = 7, loop = true, required, validate = () => true, } = config; | ||||
|     const shortcuts = { all: 'a', invert: 'i', ...config.shortcuts }; | ||||
|     const theme = makeTheme(checkboxTheme, config.theme); | ||||
|     const firstRender = useRef(true); | ||||
|     const [status, setStatus] = useState('idle'); | ||||
|     const prefix = usePrefix({ status, theme }); | ||||
|     const [items, setItems] = useState(normalizeChoices(config.choices)); | ||||
|     const bounds = useMemo(() => { | ||||
|         const first = items.findIndex(isSelectable); | ||||
|         const last = items.findLastIndex(isSelectable); | ||||
|         if (first === -1) { | ||||
|             throw new ValidationError('[checkbox prompt] No selectable choices. All choices are disabled.'); | ||||
|         } | ||||
|         return { first, last }; | ||||
|     }, [items]); | ||||
|     const [active, setActive] = useState(bounds.first); | ||||
|     const [showHelpTip, setShowHelpTip] = useState(true); | ||||
|     const [errorMsg, setError] = useState(); | ||||
|     useKeypress(async (key) => { | ||||
|         if (isEnterKey(key)) { | ||||
|             const selection = items.filter(isChecked); | ||||
|             const isValid = await validate([...selection]); | ||||
|             if (required && !items.some(isChecked)) { | ||||
|                 setError('At least one choice must be selected'); | ||||
|             } | ||||
|             else if (isValid === true) { | ||||
|                 setStatus('done'); | ||||
|                 done(selection.map((choice) => choice.value)); | ||||
|             } | ||||
|             else { | ||||
|                 setError(isValid || 'You must select a valid value'); | ||||
|             } | ||||
|         } | ||||
|         else if (isUpKey(key) || isDownKey(key)) { | ||||
|             if (loop || | ||||
|                 (isUpKey(key) && active !== bounds.first) || | ||||
|                 (isDownKey(key) && active !== bounds.last)) { | ||||
|                 const offset = isUpKey(key) ? -1 : 1; | ||||
|                 let next = active; | ||||
|                 do { | ||||
|                     next = (next + offset + items.length) % items.length; | ||||
|                 } while (!isSelectable(items[next])); | ||||
|                 setActive(next); | ||||
|             } | ||||
|         } | ||||
|         else if (isSpaceKey(key)) { | ||||
|             setError(undefined); | ||||
|             setShowHelpTip(false); | ||||
|             setItems(items.map((choice, i) => (i === active ? toggle(choice) : choice))); | ||||
|         } | ||||
|         else if (key.name === shortcuts.all) { | ||||
|             const selectAll = items.some((choice) => isSelectable(choice) && !choice.checked); | ||||
|             setItems(items.map(check(selectAll))); | ||||
|         } | ||||
|         else if (key.name === shortcuts.invert) { | ||||
|             setItems(items.map(toggle)); | ||||
|         } | ||||
|         else if (isNumberKey(key)) { | ||||
|             const selectedIndex = Number(key.name) - 1; | ||||
|             // Find the nth item (ignoring separators)
 | ||||
|             let selectableIndex = -1; | ||||
|             const position = items.findIndex((item) => { | ||||
|                 if (Separator.isSeparator(item)) | ||||
|                     return false; | ||||
|                 selectableIndex++; | ||||
|                 return selectableIndex === selectedIndex; | ||||
|             }); | ||||
|             const selectedItem = items[position]; | ||||
|             if (selectedItem && isSelectable(selectedItem)) { | ||||
|                 setActive(position); | ||||
|                 setItems(items.map((choice, i) => (i === position ? toggle(choice) : choice))); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|     const message = theme.style.message(config.message, status); | ||||
|     let description; | ||||
|     const page = usePagination({ | ||||
|         items, | ||||
|         active, | ||||
|         renderItem({ item, isActive }) { | ||||
|             if (Separator.isSeparator(item)) { | ||||
|                 return ` ${item.separator}`; | ||||
|             } | ||||
|             if (item.disabled) { | ||||
|                 const disabledLabel = typeof item.disabled === 'string' ? item.disabled : '(disabled)'; | ||||
|                 return theme.style.disabledChoice(`${item.name} ${disabledLabel}`); | ||||
|             } | ||||
|             if (isActive) { | ||||
|                 description = item.description; | ||||
|             } | ||||
|             const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked; | ||||
|             const color = isActive ? theme.style.highlight : (x) => x; | ||||
|             const cursor = isActive ? theme.icon.cursor : ' '; | ||||
|             return color(`${cursor}${checkbox} ${item.name}`); | ||||
|         }, | ||||
|         pageSize, | ||||
|         loop, | ||||
|     }); | ||||
|     if (status === 'done') { | ||||
|         const selection = items.filter(isChecked); | ||||
|         const answer = theme.style.answer(theme.style.renderSelectedChoices(selection, items)); | ||||
|         return `${prefix} ${message} ${answer}`; | ||||
|     } | ||||
|     let helpTipTop = ''; | ||||
|     let helpTipBottom = ''; | ||||
|     if (theme.helpMode === 'always' || | ||||
|         (theme.helpMode === 'auto' && | ||||
|             showHelpTip && | ||||
|             (instructions === undefined || instructions))) { | ||||
|         if (typeof instructions === 'string') { | ||||
|             helpTipTop = instructions; | ||||
|         } | ||||
|         else { | ||||
|             const keys = [ | ||||
|                 `${theme.style.key('space')} to select`, | ||||
|                 shortcuts.all ? `${theme.style.key(shortcuts.all)} to toggle all` : '', | ||||
|                 shortcuts.invert | ||||
|                     ? `${theme.style.key(shortcuts.invert)} to invert selection` | ||||
|                     : '', | ||||
|                 `and ${theme.style.key('enter')} to proceed`, | ||||
|             ]; | ||||
|             helpTipTop = ` (Press ${keys.filter((key) => key !== '').join(', ')})`; | ||||
|         } | ||||
|         if (items.length > pageSize && | ||||
|             (theme.helpMode === 'always' || | ||||
|                 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | ||||
|                 (theme.helpMode === 'auto' && firstRender.current))) { | ||||
|             helpTipBottom = `\n${theme.style.help('(Use arrow keys to reveal more choices)')}`; | ||||
|             firstRender.current = false; | ||||
|         } | ||||
|     } | ||||
|     const choiceDescription = description | ||||
|         ? `\n${theme.style.description(description)}` | ||||
|         : ``; | ||||
|     let error = ''; | ||||
|     if (errorMsg) { | ||||
|         error = `\n${theme.style.error(errorMsg)}`; | ||||
|     } | ||||
|     return `${prefix} ${message}${helpTipTop}\n${page}${helpTipBottom}${choiceDescription}${error}${ansiEscapes.cursorHide}`; | ||||
| }); | ||||
| export { Separator } from '@inquirer/core'; | ||||
| @ -0,0 +1,3 @@ | ||||
| { | ||||
|   "type": "module" | ||||
| } | ||||
| @ -0,0 +1,112 @@ | ||||
| { | ||||
|   "name": "@inquirer/checkbox", | ||||
|   "version": "4.2.0", | ||||
|   "description": "Inquirer checkbox prompt", | ||||
|   "keywords": [ | ||||
|     "answer", | ||||
|     "answers", | ||||
|     "ask", | ||||
|     "base", | ||||
|     "cli", | ||||
|     "command", | ||||
|     "command-line", | ||||
|     "confirm", | ||||
|     "enquirer", | ||||
|     "generate", | ||||
|     "generator", | ||||
|     "hyper", | ||||
|     "input", | ||||
|     "inquire", | ||||
|     "inquirer", | ||||
|     "interface", | ||||
|     "iterm", | ||||
|     "javascript", | ||||
|     "menu", | ||||
|     "node", | ||||
|     "nodejs", | ||||
|     "prompt", | ||||
|     "promptly", | ||||
|     "prompts", | ||||
|     "question", | ||||
|     "readline", | ||||
|     "scaffold", | ||||
|     "scaffolder", | ||||
|     "scaffolding", | ||||
|     "stdin", | ||||
|     "stdout", | ||||
|     "terminal", | ||||
|     "tty", | ||||
|     "ui", | ||||
|     "yeoman", | ||||
|     "yo", | ||||
|     "zsh" | ||||
|   ], | ||||
|   "homepage": "https://github.com/SBoudrias/Inquirer.js/blob/main/packages/checkbox/README.md", | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "https://github.com/SBoudrias/Inquirer.js.git" | ||||
|   }, | ||||
|   "license": "MIT", | ||||
|   "author": "Simon Boudrias <admin@simonboudrias.com>", | ||||
|   "sideEffects": false, | ||||
|   "type": "module", | ||||
|   "exports": { | ||||
|     "./package.json": "./package.json", | ||||
|     ".": { | ||||
|       "import": { | ||||
|         "types": "./dist/esm/index.d.ts", | ||||
|         "default": "./dist/esm/index.js" | ||||
|       }, | ||||
|       "require": { | ||||
|         "types": "./dist/commonjs/index.d.ts", | ||||
|         "default": "./dist/commonjs/index.js" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "main": "./dist/commonjs/index.js", | ||||
|   "module": "./dist/esm/index.js", | ||||
|   "types": "./dist/commonjs/index.d.ts", | ||||
|   "files": [ | ||||
|     "dist" | ||||
|   ], | ||||
|   "scripts": { | ||||
|     "attw": "attw --pack", | ||||
|     "tsc": "tshy" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@inquirer/core": "^10.1.15", | ||||
|     "@inquirer/figures": "^1.0.13", | ||||
|     "@inquirer/type": "^3.0.8", | ||||
|     "ansi-escapes": "^4.3.2", | ||||
|     "yoctocolors-cjs": "^2.1.2" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@arethetypeswrong/cli": "^0.18.2", | ||||
|     "@inquirer/testing": "^2.1.49", | ||||
|     "tshy": "^3.0.2" | ||||
|   }, | ||||
|   "engines": { | ||||
|     "node": ">=18" | ||||
|   }, | ||||
|   "publishConfig": { | ||||
|     "access": "public" | ||||
|   }, | ||||
|   "tshy": { | ||||
|     "exclude": [ | ||||
|       "src/**/*.test.ts" | ||||
|     ], | ||||
|     "exports": { | ||||
|       "./package.json": "./package.json", | ||||
|       ".": "./src/index.ts" | ||||
|     } | ||||
|   }, | ||||
|   "peerDependencies": { | ||||
|     "@types/node": ">=18" | ||||
|   }, | ||||
|   "peerDependenciesMeta": { | ||||
|     "@types/node": { | ||||
|       "optional": true | ||||
|     } | ||||
|   }, | ||||
|   "gitHead": "c1a755fe8b50377b685ea5951e0794985ce8d356" | ||||
| } | ||||
| @ -0,0 +1,22 @@ | ||||
| Copyright (c) 2025 Simon Boudrias | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person | ||||
| obtaining a copy of this software and associated documentation | ||||
| files (the "Software"), to deal in the Software without | ||||
| restriction, including without limitation the rights to use, | ||||
| copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the | ||||
| Software is furnished to do so, subject to the following | ||||
| conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be | ||||
| included in all copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | ||||
| OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | ||||
| HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||||
| WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
| FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | ||||
| OTHER DEALINGS IN THE SOFTWARE. | ||||
| @ -0,0 +1,102 @@ | ||||
| # `@inquirer/confirm` | ||||
| 
 | ||||
| Simple interactive command line prompt to gather boolean input from users. | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| # Special Thanks | ||||
| 
 | ||||
| <div align="center" markdown="1"> | ||||
| 
 | ||||
| [](https://graphite.dev/?utm_source=npmjs&utm_medium=repo&utm_campaign=inquirerjs)<br> | ||||
| 
 | ||||
| ### [Graphite is the AI developer productivity platform helping teams on GitHub ship higher quality software, faster](https://graphite.dev/?utm_source=npmjs&utm_medium=repo&utm_campaign=inquirerjs) | ||||
| 
 | ||||
| </div> | ||||
| 
 | ||||
| # Installation | ||||
| 
 | ||||
| <table> | ||||
| <tr> | ||||
|   <th>npm</th> | ||||
|   <th>yarn</th> | ||||
| </tr> | ||||
| <tr> | ||||
| <td> | ||||
| 
 | ||||
| ```sh | ||||
| npm install @inquirer/prompts | ||||
| ``` | ||||
| 
 | ||||
| </td> | ||||
| <td> | ||||
| 
 | ||||
| ```sh | ||||
| yarn add @inquirer/prompts | ||||
| ``` | ||||
| 
 | ||||
| </td> | ||||
| </tr> | ||||
| <tr> | ||||
| <td colSpan="2" align="center">Or</td> | ||||
| </tr> | ||||
| <tr> | ||||
| <td> | ||||
| 
 | ||||
| ```sh | ||||
| npm install @inquirer/confirm | ||||
| ``` | ||||
| 
 | ||||
| </td> | ||||
| <td> | ||||
| 
 | ||||
| ```sh | ||||
| yarn add @inquirer/confirm | ||||
| ``` | ||||
| 
 | ||||
| </td> | ||||
| </tr> | ||||
| </table> | ||||
| 
 | ||||
| # Usage | ||||
| 
 | ||||
| ```js | ||||
| import { confirm } from '@inquirer/prompts'; | ||||
| // Or | ||||
| // import confirm from '@inquirer/confirm'; | ||||
| 
 | ||||
| const answer = await confirm({ message: 'Continue?' }); | ||||
| ``` | ||||
| 
 | ||||
| ## Options | ||||
| 
 | ||||
| | Property    | Type                    | Required | Description                                             | | ||||
| | ----------- | ----------------------- | -------- | ------------------------------------------------------- | | ||||
| | message     | `string`                | yes      | The question to ask                                     | | ||||
| | default     | `boolean`               | no       | Default answer (true or false)                          | | ||||
| | transformer | `(boolean) => string`   | no       | Transform the prompt printed message to a custom string | | ||||
| | theme       | [See Theming](#Theming) | no       | Customize look of the prompt.                           | | ||||
| 
 | ||||
| ## Theming | ||||
| 
 | ||||
| You can theme a prompt by passing a `theme` object option. The theme object only need to includes the keys you wish to modify, we'll fallback on the defaults for the rest. | ||||
| 
 | ||||
| ```ts | ||||
| type Theme = { | ||||
|   prefix: string | { idle: string; done: string }; | ||||
|   spinner: { | ||||
|     interval: number; | ||||
|     frames: string[]; | ||||
|   }; | ||||
|   style: { | ||||
|     answer: (text: string) => string; | ||||
|     message: (text: string, status: 'idle' | 'done' | 'loading') => string; | ||||
|     defaultAnswer: (text: string) => string; | ||||
|   }; | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
| # License | ||||
| 
 | ||||
| Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/> | ||||
| Licensed under the MIT license. | ||||
| @ -0,0 +1,10 @@ | ||||
| import { type Theme } from '@inquirer/core'; | ||||
| import type { PartialDeep } from '@inquirer/type'; | ||||
| type ConfirmConfig = { | ||||
|     message: string; | ||||
|     default?: boolean; | ||||
|     transformer?: (value: boolean) => string; | ||||
|     theme?: PartialDeep<Theme>; | ||||
| }; | ||||
| declare const _default: import("@inquirer/type").Prompt<boolean, ConfirmConfig>; | ||||
| export default _default; | ||||
| @ -0,0 +1,48 @@ | ||||
| "use strict"; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| const core_1 = require("@inquirer/core"); | ||||
| function getBooleanValue(value, defaultValue) { | ||||
|     let answer = defaultValue !== false; | ||||
|     if (/^(y|yes)/i.test(value)) | ||||
|         answer = true; | ||||
|     else if (/^(n|no)/i.test(value)) | ||||
|         answer = false; | ||||
|     return answer; | ||||
| } | ||||
| function boolToString(value) { | ||||
|     return value ? 'Yes' : 'No'; | ||||
| } | ||||
| exports.default = (0, core_1.createPrompt)((config, done) => { | ||||
|     const { transformer = boolToString } = config; | ||||
|     const [status, setStatus] = (0, core_1.useState)('idle'); | ||||
|     const [value, setValue] = (0, core_1.useState)(''); | ||||
|     const theme = (0, core_1.makeTheme)(config.theme); | ||||
|     const prefix = (0, core_1.usePrefix)({ status, theme }); | ||||
|     (0, core_1.useKeypress)((key, rl) => { | ||||
|         if ((0, core_1.isEnterKey)(key)) { | ||||
|             const answer = getBooleanValue(value, config.default); | ||||
|             setValue(transformer(answer)); | ||||
|             setStatus('done'); | ||||
|             done(answer); | ||||
|         } | ||||
|         else if (key.name === 'tab') { | ||||
|             const answer = boolToString(!getBooleanValue(value, config.default)); | ||||
|             rl.clearLine(0); // Remove the tab character.
 | ||||
|             rl.write(answer); | ||||
|             setValue(answer); | ||||
|         } | ||||
|         else { | ||||
|             setValue(rl.line); | ||||
|         } | ||||
|     }); | ||||
|     let formattedValue = value; | ||||
|     let defaultValue = ''; | ||||
|     if (status === 'done') { | ||||
|         formattedValue = theme.style.answer(value); | ||||
|     } | ||||
|     else { | ||||
|         defaultValue = ` ${theme.style.defaultAnswer(config.default === false ? 'y/N' : 'Y/n')}`; | ||||
|     } | ||||
|     const message = theme.style.message(config.message, status); | ||||
|     return `${prefix} ${message}${defaultValue} ${formattedValue}`; | ||||
| }); | ||||
| @ -0,0 +1,3 @@ | ||||
| { | ||||
|   "type": "commonjs" | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| import { type Theme } from '@inquirer/core'; | ||||
| import type { PartialDeep } from '@inquirer/type'; | ||||
| type ConfirmConfig = { | ||||
|     message: string; | ||||
|     default?: boolean; | ||||
|     transformer?: (value: boolean) => string; | ||||
|     theme?: PartialDeep<Theme>; | ||||
| }; | ||||
| declare const _default: import("@inquirer/type").Prompt<boolean, ConfirmConfig>; | ||||
| export default _default; | ||||
| @ -0,0 +1,46 @@ | ||||
| import { createPrompt, useState, useKeypress, isEnterKey, usePrefix, makeTheme, } from '@inquirer/core'; | ||||
| function getBooleanValue(value, defaultValue) { | ||||
|     let answer = defaultValue !== false; | ||||
|     if (/^(y|yes)/i.test(value)) | ||||
|         answer = true; | ||||
|     else if (/^(n|no)/i.test(value)) | ||||
|         answer = false; | ||||
|     return answer; | ||||
| } | ||||
| function boolToString(value) { | ||||
|     return value ? 'Yes' : 'No'; | ||||
| } | ||||
| export default createPrompt((config, done) => { | ||||
|     const { transformer = boolToString } = config; | ||||
|     const [status, setStatus] = useState('idle'); | ||||
|     const [value, setValue] = useState(''); | ||||
|     const theme = makeTheme(config.theme); | ||||
|     const prefix = usePrefix({ status, theme }); | ||||
|     useKeypress((key, rl) => { | ||||
|         if (isEnterKey(key)) { | ||||
|             const answer = getBooleanValue(value, config.default); | ||||
|             setValue(transformer(answer)); | ||||
|             setStatus('done'); | ||||
|             done(answer); | ||||
|         } | ||||
|         else if (key.name === 'tab') { | ||||
|             const answer = boolToString(!getBooleanValue(value, config.default)); | ||||
|             rl.clearLine(0); // Remove the tab character.
 | ||||
|             rl.write(answer); | ||||
|             setValue(answer); | ||||
|         } | ||||
|         else { | ||||
|             setValue(rl.line); | ||||
|         } | ||||
|     }); | ||||
|     let formattedValue = value; | ||||
|     let defaultValue = ''; | ||||
|     if (status === 'done') { | ||||
|         formattedValue = theme.style.answer(value); | ||||
|     } | ||||
|     else { | ||||
|         defaultValue = ` ${theme.style.defaultAnswer(config.default === false ? 'y/N' : 'Y/n')}`; | ||||
|     } | ||||
|     const message = theme.style.message(config.message, status); | ||||
|     return `${prefix} ${message}${defaultValue} ${formattedValue}`; | ||||
| }); | ||||
| @ -0,0 +1,3 @@ | ||||
| { | ||||
|   "type": "module" | ||||
| } | ||||
| @ -0,0 +1,109 @@ | ||||
| { | ||||
|   "name": "@inquirer/confirm", | ||||
|   "version": "5.1.14", | ||||
|   "description": "Inquirer confirm prompt", | ||||
|   "keywords": [ | ||||
|     "answer", | ||||
|     "answers", | ||||
|     "ask", | ||||
|     "base", | ||||
|     "cli", | ||||
|     "command", | ||||
|     "command-line", | ||||
|     "confirm", | ||||
|     "enquirer", | ||||
|     "generate", | ||||
|     "generator", | ||||
|     "hyper", | ||||
|     "input", | ||||
|     "inquire", | ||||
|     "inquirer", | ||||
|     "interface", | ||||
|     "iterm", | ||||
|     "javascript", | ||||
|     "menu", | ||||
|     "node", | ||||
|     "nodejs", | ||||
|     "prompt", | ||||
|     "promptly", | ||||
|     "prompts", | ||||
|     "question", | ||||
|     "readline", | ||||
|     "scaffold", | ||||
|     "scaffolder", | ||||
|     "scaffolding", | ||||
|     "stdin", | ||||
|     "stdout", | ||||
|     "terminal", | ||||
|     "tty", | ||||
|     "ui", | ||||
|     "yeoman", | ||||
|     "yo", | ||||
|     "zsh" | ||||
|   ], | ||||
|   "homepage": "https://github.com/SBoudrias/Inquirer.js/blob/main/packages/confirm/README.md", | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "https://github.com/SBoudrias/Inquirer.js.git" | ||||
|   }, | ||||
|   "license": "MIT", | ||||
|   "author": "Simon Boudrias <admin@simonboudrias.com>", | ||||
|   "sideEffects": false, | ||||
|   "type": "module", | ||||
|   "exports": { | ||||
|     "./package.json": "./package.json", | ||||
|     ".": { | ||||
|       "import": { | ||||
|         "types": "./dist/esm/index.d.ts", | ||||
|         "default": "./dist/esm/index.js" | ||||
|       }, | ||||
|       "require": { | ||||
|         "types": "./dist/commonjs/index.d.ts", | ||||
|         "default": "./dist/commonjs/index.js" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "main": "./dist/commonjs/index.js", | ||||
|   "module": "./dist/esm/index.js", | ||||
|   "types": "./dist/commonjs/index.d.ts", | ||||
|   "files": [ | ||||
|     "dist" | ||||
|   ], | ||||
|   "scripts": { | ||||
|     "attw": "attw --pack", | ||||
|     "tsc": "tshy" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@inquirer/core": "^10.1.15", | ||||
|     "@inquirer/type": "^3.0.8" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@arethetypeswrong/cli": "^0.18.2", | ||||
|     "@inquirer/testing": "^2.1.49", | ||||
|     "tshy": "^3.0.2" | ||||
|   }, | ||||
|   "engines": { | ||||
|     "node": ">=18" | ||||
|   }, | ||||
|   "publishConfig": { | ||||
|     "access": "public" | ||||
|   }, | ||||
|   "tshy": { | ||||
|     "exclude": [ | ||||
|       "src/**/*.test.ts" | ||||
|     ], | ||||
|     "exports": { | ||||
|       "./package.json": "./package.json", | ||||
|       ".": "./src/index.ts" | ||||
|     } | ||||
|   }, | ||||
|   "peerDependencies": { | ||||
|     "@types/node": ">=18" | ||||
|   }, | ||||
|   "peerDependenciesMeta": { | ||||
|     "@types/node": { | ||||
|       "optional": true | ||||
|     } | ||||
|   }, | ||||
|   "gitHead": "c1a755fe8b50377b685ea5951e0794985ce8d356" | ||||
| } | ||||
| @ -0,0 +1,22 @@ | ||||
| Copyright (c) 2025 Simon Boudrias | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person | ||||
| obtaining a copy of this software and associated documentation | ||||
| files (the "Software"), to deal in the Software without | ||||
| restriction, including without limitation the rights to use, | ||||
| copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the | ||||
| Software is furnished to do so, subject to the following | ||||
| conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be | ||||
| included in all copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | ||||
| OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | ||||
| HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||||
| WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
| FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | ||||
| OTHER DEALINGS IN THE SOFTWARE. | ||||
| @ -0,0 +1,13 @@ | ||||
| export * from './lib/key.ts'; | ||||
| export * from './lib/errors.ts'; | ||||
| export { usePrefix } from './lib/use-prefix.ts'; | ||||
| export { useState } from './lib/use-state.ts'; | ||||
| export { useEffect } from './lib/use-effect.ts'; | ||||
| export { useMemo } from './lib/use-memo.ts'; | ||||
| export { useRef } from './lib/use-ref.ts'; | ||||
| export { useKeypress } from './lib/use-keypress.ts'; | ||||
| export { makeTheme } from './lib/make-theme.ts'; | ||||
| export type { Theme, Status } from './lib/theme.ts'; | ||||
| export { usePagination } from './lib/pagination/use-pagination.ts'; | ||||
| export { createPrompt } from './lib/create-prompt.ts'; | ||||
| export { Separator } from './lib/Separator.ts'; | ||||
| @ -0,0 +1,39 @@ | ||||
| "use strict"; | ||||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||||
|     if (k2 === undefined) k2 = k; | ||||
|     var desc = Object.getOwnPropertyDescriptor(m, k); | ||||
|     if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||||
|       desc = { enumerable: true, get: function() { return m[k]; } }; | ||||
|     } | ||||
|     Object.defineProperty(o, k2, desc); | ||||
| }) : (function(o, m, k, k2) { | ||||
|     if (k2 === undefined) k2 = k; | ||||
|     o[k2] = m[k]; | ||||
| })); | ||||
| var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||||
|     for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.Separator = exports.createPrompt = exports.usePagination = exports.makeTheme = exports.useKeypress = exports.useRef = exports.useMemo = exports.useEffect = exports.useState = exports.usePrefix = void 0; | ||||
| __exportStar(require("./lib/key.js"), exports); | ||||
| __exportStar(require("./lib/errors.js"), exports); | ||||
| var use_prefix_ts_1 = require("./lib/use-prefix.js"); | ||||
| Object.defineProperty(exports, "usePrefix", { enumerable: true, get: function () { return use_prefix_ts_1.usePrefix; } }); | ||||
| var use_state_ts_1 = require("./lib/use-state.js"); | ||||
| Object.defineProperty(exports, "useState", { enumerable: true, get: function () { return use_state_ts_1.useState; } }); | ||||
| var use_effect_ts_1 = require("./lib/use-effect.js"); | ||||
| Object.defineProperty(exports, "useEffect", { enumerable: true, get: function () { return use_effect_ts_1.useEffect; } }); | ||||
| var use_memo_ts_1 = require("./lib/use-memo.js"); | ||||
| Object.defineProperty(exports, "useMemo", { enumerable: true, get: function () { return use_memo_ts_1.useMemo; } }); | ||||
| var use_ref_ts_1 = require("./lib/use-ref.js"); | ||||
| Object.defineProperty(exports, "useRef", { enumerable: true, get: function () { return use_ref_ts_1.useRef; } }); | ||||
| var use_keypress_ts_1 = require("./lib/use-keypress.js"); | ||||
| Object.defineProperty(exports, "useKeypress", { enumerable: true, get: function () { return use_keypress_ts_1.useKeypress; } }); | ||||
| var make_theme_ts_1 = require("./lib/make-theme.js"); | ||||
| Object.defineProperty(exports, "makeTheme", { enumerable: true, get: function () { return make_theme_ts_1.makeTheme; } }); | ||||
| var use_pagination_ts_1 = require("./lib/pagination/use-pagination.js"); | ||||
| Object.defineProperty(exports, "usePagination", { enumerable: true, get: function () { return use_pagination_ts_1.usePagination; } }); | ||||
| var create_prompt_ts_1 = require("./lib/create-prompt.js"); | ||||
| Object.defineProperty(exports, "createPrompt", { enumerable: true, get: function () { return create_prompt_ts_1.createPrompt; } }); | ||||
| var Separator_ts_1 = require("./lib/Separator.js"); | ||||
| Object.defineProperty(exports, "Separator", { enumerable: true, get: function () { return Separator_ts_1.Separator; } }); | ||||
| @ -0,0 +1,10 @@ | ||||
| /** | ||||
|  * Separator object | ||||
|  * Used to space/separate choices group | ||||
|  */ | ||||
| export declare class Separator { | ||||
|     readonly separator: string; | ||||
|     readonly type: string; | ||||
|     constructor(separator?: string); | ||||
|     static isSeparator(choice: unknown): choice is Separator; | ||||
| } | ||||
| @ -0,0 +1,28 @@ | ||||
| "use strict"; | ||||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||||
|     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.Separator = void 0; | ||||
| const yoctocolors_cjs_1 = __importDefault(require("yoctocolors-cjs")); | ||||
| const figures_1 = __importDefault(require("@inquirer/figures")); | ||||
| /** | ||||
|  * Separator object | ||||
|  * Used to space/separate choices group | ||||
|  */ | ||||
| class Separator { | ||||
|     separator = yoctocolors_cjs_1.default.dim(Array.from({ length: 15 }).join(figures_1.default.line)); | ||||
|     type = 'separator'; | ||||
|     constructor(separator) { | ||||
|         if (separator) { | ||||
|             this.separator = separator; | ||||
|         } | ||||
|     } | ||||
|     static isSeparator(choice) { | ||||
|         return Boolean(choice && | ||||
|             typeof choice === 'object' && | ||||
|             'type' in choice && | ||||
|             choice.type === 'separator'); | ||||
|     } | ||||
| } | ||||
| exports.Separator = Separator; | ||||
| @ -0,0 +1,4 @@ | ||||
| import { type Prompt, type Prettify } from '@inquirer/type'; | ||||
| type ViewFunction<Value, Config> = (config: Prettify<Config>, done: (value: Value) => void) => string | [string, string | undefined]; | ||||
| export declare function createPrompt<Value, Config>(view: ViewFunction<Value, Config>): Prompt<Value, Config>; | ||||
| export {}; | ||||
| @ -0,0 +1,156 @@ | ||||
| "use strict"; | ||||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||||
|     if (k2 === undefined) k2 = k; | ||||
|     var desc = Object.getOwnPropertyDescriptor(m, k); | ||||
|     if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||||
|       desc = { enumerable: true, get: function() { return m[k]; } }; | ||||
|     } | ||||
|     Object.defineProperty(o, k2, desc); | ||||
| }) : (function(o, m, k, k2) { | ||||
|     if (k2 === undefined) k2 = k; | ||||
|     o[k2] = m[k]; | ||||
| })); | ||||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||||
|     Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||||
| }) : function(o, v) { | ||||
|     o["default"] = v; | ||||
| }); | ||||
| var __importStar = (this && this.__importStar) || (function () { | ||||
|     var ownKeys = function(o) { | ||||
|         ownKeys = Object.getOwnPropertyNames || function (o) { | ||||
|             var ar = []; | ||||
|             for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; | ||||
|             return ar; | ||||
|         }; | ||||
|         return ownKeys(o); | ||||
|     }; | ||||
|     return function (mod) { | ||||
|         if (mod && mod.__esModule) return mod; | ||||
|         var result = {}; | ||||
|         if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); | ||||
|         __setModuleDefault(result, mod); | ||||
|         return result; | ||||
|     }; | ||||
| })(); | ||||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||||
|     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.createPrompt = createPrompt; | ||||
| const readline = __importStar(require("node:readline")); | ||||
| const node_async_hooks_1 = require("node:async_hooks"); | ||||
| const mute_stream_1 = __importDefault(require("mute-stream")); | ||||
| const signal_exit_1 = require("signal-exit"); | ||||
| const screen_manager_ts_1 = __importDefault(require("./screen-manager.js")); | ||||
| const promise_polyfill_ts_1 = require("./promise-polyfill.js"); | ||||
| const hook_engine_ts_1 = require("./hook-engine.js"); | ||||
| const errors_ts_1 = require("./errors.js"); | ||||
| function getCallSites() { | ||||
|     // eslint-disable-next-line @typescript-eslint/unbound-method
 | ||||
|     const _prepareStackTrace = Error.prepareStackTrace; | ||||
|     let result = []; | ||||
|     try { | ||||
|         Error.prepareStackTrace = (_, callSites) => { | ||||
|             const callSitesWithoutCurrent = callSites.slice(1); | ||||
|             result = callSitesWithoutCurrent; | ||||
|             return callSitesWithoutCurrent; | ||||
|         }; | ||||
|         // eslint-disable-next-line @typescript-eslint/no-unused-expressions
 | ||||
|         new Error().stack; | ||||
|     } | ||||
|     catch { | ||||
|         // An error will occur if the Node flag --frozen-intrinsics is used.
 | ||||
|         // https://nodejs.org/api/cli.html#--frozen-intrinsics
 | ||||
|         return result; | ||||
|     } | ||||
|     Error.prepareStackTrace = _prepareStackTrace; | ||||
|     return result; | ||||
| } | ||||
| function createPrompt(view) { | ||||
|     const callSites = getCallSites(); | ||||
|     const prompt = (config, context = {}) => { | ||||
|         // Default `input` to stdin
 | ||||
|         const { input = process.stdin, signal } = context; | ||||
|         const cleanups = new Set(); | ||||
|         // Add mute capabilities to the output
 | ||||
|         const output = new mute_stream_1.default(); | ||||
|         output.pipe(context.output ?? process.stdout); | ||||
|         const rl = readline.createInterface({ | ||||
|             terminal: true, | ||||
|             input, | ||||
|             output, | ||||
|         }); | ||||
|         const screen = new screen_manager_ts_1.default(rl); | ||||
|         const { promise, resolve, reject } = promise_polyfill_ts_1.PromisePolyfill.withResolver(); | ||||
|         const cancel = () => reject(new errors_ts_1.CancelPromptError()); | ||||
|         if (signal) { | ||||
|             const abort = () => reject(new errors_ts_1.AbortPromptError({ cause: signal.reason })); | ||||
|             if (signal.aborted) { | ||||
|                 abort(); | ||||
|                 return Object.assign(promise, { cancel }); | ||||
|             } | ||||
|             signal.addEventListener('abort', abort); | ||||
|             cleanups.add(() => signal.removeEventListener('abort', abort)); | ||||
|         } | ||||
|         cleanups.add((0, signal_exit_1.onExit)((code, signal) => { | ||||
|             reject(new errors_ts_1.ExitPromptError(`User force closed the prompt with ${code} ${signal}`)); | ||||
|         })); | ||||
|         // SIGINT must be explicitly handled by the prompt so the ExitPromptError can be handled.
 | ||||
|         // Otherwise, the prompt will stop and in some scenarios never resolve.
 | ||||
|         // Ref issue #1741
 | ||||
|         const sigint = () => reject(new errors_ts_1.ExitPromptError(`User force closed the prompt with SIGINT`)); | ||||
|         rl.on('SIGINT', sigint); | ||||
|         cleanups.add(() => rl.removeListener('SIGINT', sigint)); | ||||
|         // Re-renders only happen when the state change; but the readline cursor could change position
 | ||||
|         // and that also requires a re-render (and a manual one because we mute the streams).
 | ||||
|         // We set the listener after the initial workLoop to avoid a double render if render triggered
 | ||||
|         // by a state change sets the cursor to the right position.
 | ||||
|         const checkCursorPos = () => screen.checkCursorPos(); | ||||
|         rl.input.on('keypress', checkCursorPos); | ||||
|         cleanups.add(() => rl.input.removeListener('keypress', checkCursorPos)); | ||||
|         return (0, hook_engine_ts_1.withHooks)(rl, (cycle) => { | ||||
|             // The close event triggers immediately when the user press ctrl+c. SignalExit on the other hand
 | ||||
|             // triggers after the process is done (which happens after timeouts are done triggering.)
 | ||||
|             // We triggers the hooks cleanup phase on rl `close` so active timeouts can be cleared.
 | ||||
|             const hooksCleanup = node_async_hooks_1.AsyncResource.bind(() => hook_engine_ts_1.effectScheduler.clearAll()); | ||||
|             rl.on('close', hooksCleanup); | ||||
|             cleanups.add(() => rl.removeListener('close', hooksCleanup)); | ||||
|             cycle(() => { | ||||
|                 try { | ||||
|                     const nextView = view(config, (value) => { | ||||
|                         setImmediate(() => resolve(value)); | ||||
|                     }); | ||||
|                     // Typescript won't allow this, but not all users rely on typescript.
 | ||||
|                     // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | ||||
|                     if (nextView === undefined) { | ||||
|                         const callerFilename = callSites[1]?.getFileName(); | ||||
|                         throw new Error(`Prompt functions must return a string.\n    at ${callerFilename}`); | ||||
|                     } | ||||
|                     const [content, bottomContent] = typeof nextView === 'string' ? [nextView] : nextView; | ||||
|                     screen.render(content, bottomContent); | ||||
|                     hook_engine_ts_1.effectScheduler.run(); | ||||
|                 } | ||||
|                 catch (error) { | ||||
|                     reject(error); | ||||
|                 } | ||||
|             }); | ||||
|             return Object.assign(promise | ||||
|                 .then((answer) => { | ||||
|                 hook_engine_ts_1.effectScheduler.clearAll(); | ||||
|                 return answer; | ||||
|             }, (error) => { | ||||
|                 hook_engine_ts_1.effectScheduler.clearAll(); | ||||
|                 throw error; | ||||
|             }) | ||||
|                 // Wait for the promise to settle, then cleanup.
 | ||||
|                 .finally(() => { | ||||
|                 cleanups.forEach((cleanup) => cleanup()); | ||||
|                 screen.done({ clearContent: Boolean(context.clearPromptOnDone) }); | ||||
|                 output.end(); | ||||
|             }) | ||||
|                 // Once cleanup is done, let the expose promise resolve/reject to the internal one.
 | ||||
|                 .then(() => promise), { cancel }); | ||||
|         }); | ||||
|     }; | ||||
|     return prompt; | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| export declare class AbortPromptError extends Error { | ||||
|     name: string; | ||||
|     message: string; | ||||
|     constructor(options?: { | ||||
|         cause?: unknown; | ||||
|     }); | ||||
| } | ||||
| export declare class CancelPromptError extends Error { | ||||
|     name: string; | ||||
|     message: string; | ||||
| } | ||||
| export declare class ExitPromptError extends Error { | ||||
|     name: string; | ||||
| } | ||||
| export declare class HookError extends Error { | ||||
|     name: string; | ||||
| } | ||||
| export declare class ValidationError extends Error { | ||||
|     name: string; | ||||
| } | ||||
| @ -0,0 +1,29 @@ | ||||
| "use strict"; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.ValidationError = exports.HookError = exports.ExitPromptError = exports.CancelPromptError = exports.AbortPromptError = void 0; | ||||
| class AbortPromptError extends Error { | ||||
|     name = 'AbortPromptError'; | ||||
|     message = 'Prompt was aborted'; | ||||
|     constructor(options) { | ||||
|         super(); | ||||
|         this.cause = options?.cause; | ||||
|     } | ||||
| } | ||||
| exports.AbortPromptError = AbortPromptError; | ||||
| class CancelPromptError extends Error { | ||||
|     name = 'CancelPromptError'; | ||||
|     message = 'Prompt was canceled'; | ||||
| } | ||||
| exports.CancelPromptError = CancelPromptError; | ||||
| class ExitPromptError extends Error { | ||||
|     name = 'ExitPromptError'; | ||||
| } | ||||
| exports.ExitPromptError = ExitPromptError; | ||||
| class HookError extends Error { | ||||
|     name = 'HookError'; | ||||
| } | ||||
| exports.HookError = HookError; | ||||
| class ValidationError extends Error { | ||||
|     name = 'ValidationError'; | ||||
| } | ||||
| exports.ValidationError = ValidationError; | ||||
| @ -0,0 +1,23 @@ | ||||
| import type { InquirerReadline } from '@inquirer/type'; | ||||
| export declare function withHooks<T>(rl: InquirerReadline, cb: (cycle: (render: () => void) => void) => T): T; | ||||
| export declare function readline(): InquirerReadline; | ||||
| export declare function withUpdates<Args extends unknown[], R>(fn: (...args: Args) => R): (...args: Args) => R; | ||||
| type SetPointer<Value> = { | ||||
|     get(): Value; | ||||
|     set(value: Value): void; | ||||
|     initialized: true; | ||||
| }; | ||||
| type UnsetPointer<Value> = { | ||||
|     get(): void; | ||||
|     set(value: Value): void; | ||||
|     initialized: false; | ||||
| }; | ||||
| type Pointer<Value> = SetPointer<Value> | UnsetPointer<Value>; | ||||
| export declare function withPointer<Value, ReturnValue>(cb: (pointer: Pointer<Value>) => ReturnValue): ReturnValue; | ||||
| export declare function handleChange(): void; | ||||
| export declare const effectScheduler: { | ||||
|     queue(cb: (readline: InquirerReadline) => void | (() => void)): void; | ||||
|     run(): void; | ||||
|     clearAll(): void; | ||||
| }; | ||||
| export {}; | ||||
| @ -0,0 +1,118 @@ | ||||
| "use strict"; | ||||
| /* eslint @typescript-eslint/no-explicit-any: ["off"] */ | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.effectScheduler = void 0; | ||||
| exports.withHooks = withHooks; | ||||
| exports.readline = readline; | ||||
| exports.withUpdates = withUpdates; | ||||
| exports.withPointer = withPointer; | ||||
| exports.handleChange = handleChange; | ||||
| const node_async_hooks_1 = require("node:async_hooks"); | ||||
| const errors_ts_1 = require("./errors.js"); | ||||
| const hookStorage = new node_async_hooks_1.AsyncLocalStorage(); | ||||
| function createStore(rl) { | ||||
|     const store = { | ||||
|         rl, | ||||
|         hooks: [], | ||||
|         hooksCleanup: [], | ||||
|         hooksEffect: [], | ||||
|         index: 0, | ||||
|         handleChange() { }, | ||||
|     }; | ||||
|     return store; | ||||
| } | ||||
| // Run callback in with the hook engine setup.
 | ||||
| function withHooks(rl, cb) { | ||||
|     const store = createStore(rl); | ||||
|     return hookStorage.run(store, () => { | ||||
|         function cycle(render) { | ||||
|             store.handleChange = () => { | ||||
|                 store.index = 0; | ||||
|                 render(); | ||||
|             }; | ||||
|             store.handleChange(); | ||||
|         } | ||||
|         return cb(cycle); | ||||
|     }); | ||||
| } | ||||
| // Safe getStore utility that'll return the store or throw if undefined.
 | ||||
| function getStore() { | ||||
|     const store = hookStorage.getStore(); | ||||
|     if (!store) { | ||||
|         throw new errors_ts_1.HookError('[Inquirer] Hook functions can only be called from within a prompt'); | ||||
|     } | ||||
|     return store; | ||||
| } | ||||
| function readline() { | ||||
|     return getStore().rl; | ||||
| } | ||||
| // Merge state updates happening within the callback function to avoid multiple renders.
 | ||||
| function withUpdates(fn) { | ||||
|     const wrapped = (...args) => { | ||||
|         const store = getStore(); | ||||
|         let shouldUpdate = false; | ||||
|         const oldHandleChange = store.handleChange; | ||||
|         store.handleChange = () => { | ||||
|             shouldUpdate = true; | ||||
|         }; | ||||
|         const returnValue = fn(...args); | ||||
|         if (shouldUpdate) { | ||||
|             oldHandleChange(); | ||||
|         } | ||||
|         store.handleChange = oldHandleChange; | ||||
|         return returnValue; | ||||
|     }; | ||||
|     return node_async_hooks_1.AsyncResource.bind(wrapped); | ||||
| } | ||||
| function withPointer(cb) { | ||||
|     const store = getStore(); | ||||
|     const { index } = store; | ||||
|     const pointer = { | ||||
|         get() { | ||||
|             return store.hooks[index]; | ||||
|         }, | ||||
|         set(value) { | ||||
|             store.hooks[index] = value; | ||||
|         }, | ||||
|         initialized: index in store.hooks, | ||||
|     }; | ||||
|     const returnValue = cb(pointer); | ||||
|     store.index++; | ||||
|     return returnValue; | ||||
| } | ||||
| function handleChange() { | ||||
|     getStore().handleChange(); | ||||
| } | ||||
| exports.effectScheduler = { | ||||
|     queue(cb) { | ||||
|         const store = getStore(); | ||||
|         const { index } = store; | ||||
|         store.hooksEffect.push(() => { | ||||
|             store.hooksCleanup[index]?.(); | ||||
|             const cleanFn = cb(readline()); | ||||
|             if (cleanFn != null && typeof cleanFn !== 'function') { | ||||
|                 throw new errors_ts_1.ValidationError('useEffect return value must be a cleanup function or nothing.'); | ||||
|             } | ||||
|             store.hooksCleanup[index] = cleanFn; | ||||
|         }); | ||||
|     }, | ||||
|     run() { | ||||
|         const store = getStore(); | ||||
|         withUpdates(() => { | ||||
|             store.hooksEffect.forEach((effect) => { | ||||
|                 effect(); | ||||
|             }); | ||||
|             // Warning: Clean the hooks before exiting the `withUpdates` block.
 | ||||
|             // Failure to do so means an updates would hit the same effects again.
 | ||||
|             store.hooksEffect.length = 0; | ||||
|         })(); | ||||
|     }, | ||||
|     clearAll() { | ||||
|         const store = getStore(); | ||||
|         store.hooksCleanup.forEach((cleanFn) => { | ||||
|             cleanFn?.(); | ||||
|         }); | ||||
|         store.hooksEffect.length = 0; | ||||
|         store.hooksCleanup.length = 0; | ||||
|     }, | ||||
| }; | ||||
| @ -0,0 +1,10 @@ | ||||
| export type KeypressEvent = { | ||||
|     name: string; | ||||
|     ctrl: boolean; | ||||
| }; | ||||
| export declare const isUpKey: (key: KeypressEvent) => boolean; | ||||
| export declare const isDownKey: (key: KeypressEvent) => boolean; | ||||
| export declare const isSpaceKey: (key: KeypressEvent) => boolean; | ||||
| export declare const isBackspaceKey: (key: KeypressEvent) => boolean; | ||||
| export declare const isNumberKey: (key: KeypressEvent) => boolean; | ||||
| export declare const isEnterKey: (key: KeypressEvent) => boolean; | ||||
| @ -0,0 +1,27 @@ | ||||
| "use strict"; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.isEnterKey = exports.isNumberKey = exports.isBackspaceKey = exports.isSpaceKey = exports.isDownKey = exports.isUpKey = void 0; | ||||
| const isUpKey = (key) =>  | ||||
| // The up key
 | ||||
| key.name === 'up' || | ||||
|     // Vim keybinding
 | ||||
|     key.name === 'k' || | ||||
|     // Emacs keybinding
 | ||||
|     (key.ctrl && key.name === 'p'); | ||||
| exports.isUpKey = isUpKey; | ||||
| const isDownKey = (key) =>  | ||||
| // The down key
 | ||||
| key.name === 'down' || | ||||
|     // Vim keybinding
 | ||||
|     key.name === 'j' || | ||||
|     // Emacs keybinding
 | ||||
|     (key.ctrl && key.name === 'n'); | ||||
| exports.isDownKey = isDownKey; | ||||
| const isSpaceKey = (key) => key.name === 'space'; | ||||
| exports.isSpaceKey = isSpaceKey; | ||||
| const isBackspaceKey = (key) => key.name === 'backspace'; | ||||
| exports.isBackspaceKey = isBackspaceKey; | ||||
| const isNumberKey = (key) => '1234567890'.includes(key.name); | ||||
| exports.isNumberKey = isNumberKey; | ||||
| const isEnterKey = (key) => key.name === 'enter' || key.name === 'return'; | ||||
| exports.isEnterKey = isEnterKey; | ||||
| @ -0,0 +1,3 @@ | ||||
| import type { Prettify, PartialDeep } from '@inquirer/type'; | ||||
| import { type Theme } from './theme.ts'; | ||||
| export declare function makeTheme<SpecificTheme extends object>(...themes: ReadonlyArray<undefined | PartialDeep<Theme<SpecificTheme>>>): Prettify<Theme<SpecificTheme>>; | ||||
| @ -0,0 +1,33 @@ | ||||
| "use strict"; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.makeTheme = makeTheme; | ||||
| const theme_ts_1 = require("./theme.js"); | ||||
| function isPlainObject(value) { | ||||
|     if (typeof value !== 'object' || value === null) | ||||
|         return false; | ||||
|     let proto = value; | ||||
|     while (Object.getPrototypeOf(proto) !== null) { | ||||
|         proto = Object.getPrototypeOf(proto); | ||||
|     } | ||||
|     return Object.getPrototypeOf(value) === proto; | ||||
| } | ||||
| function deepMerge(...objects) { | ||||
|     const output = {}; | ||||
|     for (const obj of objects) { | ||||
|         for (const [key, value] of Object.entries(obj)) { | ||||
|             const prevValue = output[key]; | ||||
|             output[key] = | ||||
|                 isPlainObject(prevValue) && isPlainObject(value) | ||||
|                     ? deepMerge(prevValue, value) | ||||
|                     : value; | ||||
|         } | ||||
|     } | ||||
|     return output; | ||||
| } | ||||
| function makeTheme(...themes) { | ||||
|     const themesToMerge = [ | ||||
|         theme_ts_1.defaultTheme, | ||||
|         ...themes.filter((theme) => theme != null), | ||||
|     ]; | ||||
|     return deepMerge(...themesToMerge); | ||||
| } | ||||
| @ -0,0 +1,16 @@ | ||||
| import type { Prettify } from '@inquirer/type'; | ||||
| export declare function usePagination<T>({ items, active, renderItem, pageSize, loop, }: { | ||||
|     items: ReadonlyArray<T>; | ||||
|     /** The index of the active item. */ | ||||
|     active: number; | ||||
|     /** Renders an item as part of a page. */ | ||||
|     renderItem: (layout: Prettify<{ | ||||
|         item: T; | ||||
|         index: number; | ||||
|         isActive: boolean; | ||||
|     }>) => string; | ||||
|     /** The size of the page. */ | ||||
|     pageSize: number; | ||||
|     /** Allows creating an infinitely looping list. `true` if unspecified. */ | ||||
|     loop?: boolean; | ||||
| }): string; | ||||
| @ -0,0 +1,124 @@ | ||||
| "use strict"; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.usePagination = usePagination; | ||||
| const use_ref_ts_1 = require("../use-ref.js"); | ||||
| const utils_ts_1 = require("../utils.js"); | ||||
| function usePointerPosition({ active, renderedItems, pageSize, loop, }) { | ||||
|     const state = (0, use_ref_ts_1.useRef)({ | ||||
|         lastPointer: active, | ||||
|         lastActive: undefined, | ||||
|     }); | ||||
|     const { lastPointer, lastActive } = state.current; | ||||
|     const middle = Math.floor(pageSize / 2); | ||||
|     const renderedLength = renderedItems.reduce((acc, item) => acc + item.length, 0); | ||||
|     const defaultPointerPosition = renderedItems | ||||
|         .slice(0, active) | ||||
|         .reduce((acc, item) => acc + item.length, 0); | ||||
|     let pointer = defaultPointerPosition; | ||||
|     if (renderedLength > pageSize) { | ||||
|         if (loop) { | ||||
|             /** | ||||
|              * Creates the next position for the pointer considering an infinitely | ||||
|              * looping list of items to be rendered on the page. | ||||
|              * | ||||
|              * The goal is to progressively move the cursor to the middle position as the user move down, and then keep | ||||
|              * the cursor there. When the user move up, maintain the cursor position. | ||||
|              */ | ||||
|             // By default, keep the cursor position as-is.
 | ||||
|             pointer = lastPointer; | ||||
|             if ( | ||||
|             // First render, skip this logic.
 | ||||
|             lastActive != null && | ||||
|                 // Only move the pointer down when the user moves down.
 | ||||
|                 lastActive < active && | ||||
|                 // Check user didn't move up across page boundary.
 | ||||
|                 active - lastActive < pageSize) { | ||||
|                 pointer = Math.min( | ||||
|                 // Furthest allowed position for the pointer is the middle of the list
 | ||||
|                 middle, Math.abs(active - lastActive) === 1 | ||||
|                     ? Math.min( | ||||
|                     // Move the pointer at most the height of the last active item.
 | ||||
|                     lastPointer + (renderedItems[lastActive]?.length ?? 0),  | ||||
|                     // If the user moved by one item, move the pointer to the natural position of the active item as
 | ||||
|                     // long as it doesn't move the cursor up.
 | ||||
|                     Math.max(defaultPointerPosition, lastPointer)) | ||||
|                     : // Otherwise, move the pointer down by the difference between the active and last active item.
 | ||||
|                         lastPointer + active - lastActive); | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             /** | ||||
|              * Creates the next position for the pointer considering a finite list of | ||||
|              * items to be rendered on a page. | ||||
|              * | ||||
|              * The goal is to keep the pointer in the middle of the page whenever possible, until | ||||
|              * we reach the bounds of the list (top or bottom). In which case, the cursor moves progressively | ||||
|              * to the bottom or top of the list. | ||||
|              */ | ||||
|             const spaceUnderActive = renderedItems | ||||
|                 .slice(active) | ||||
|                 .reduce((acc, item) => acc + item.length, 0); | ||||
|             pointer = | ||||
|                 spaceUnderActive < pageSize - middle | ||||
|                     ? // If the active item is near the end of the list, progressively move the cursor towards the end.
 | ||||
|                         pageSize - spaceUnderActive | ||||
|                     : // Otherwise, progressively move the pointer to the middle of the list.
 | ||||
|                         Math.min(defaultPointerPosition, middle); | ||||
|         } | ||||
|     } | ||||
|     // Save state for the next render
 | ||||
|     state.current.lastPointer = pointer; | ||||
|     state.current.lastActive = active; | ||||
|     return pointer; | ||||
| } | ||||
| function usePagination({ items, active, renderItem, pageSize, loop = true, }) { | ||||
|     const width = (0, utils_ts_1.readlineWidth)(); | ||||
|     const bound = (num) => ((num % items.length) + items.length) % items.length; | ||||
|     const renderedItems = items.map((item, index) => { | ||||
|         if (item == null) | ||||
|             return []; | ||||
|         return (0, utils_ts_1.breakLines)(renderItem({ item, index, isActive: index === active }), width).split('\n'); | ||||
|     }); | ||||
|     const renderedLength = renderedItems.reduce((acc, item) => acc + item.length, 0); | ||||
|     const renderItemAtIndex = (index) => renderedItems[index] ?? []; | ||||
|     const pointer = usePointerPosition({ active, renderedItems, pageSize, loop }); | ||||
|     // Render the active item to decide the position.
 | ||||
|     // If the active item fits under the pointer, we render it there.
 | ||||
|     // Otherwise, we need to render it to fit at the bottom of the page; moving the pointer up.
 | ||||
|     const activeItem = renderItemAtIndex(active).slice(0, pageSize); | ||||
|     const activeItemPosition = pointer + activeItem.length <= pageSize ? pointer : pageSize - activeItem.length; | ||||
|     // Create an array of lines for the page, and add the lines of the active item into the page
 | ||||
|     const pageBuffer = Array.from({ length: pageSize }); | ||||
|     pageBuffer.splice(activeItemPosition, activeItem.length, ...activeItem); | ||||
|     // Store to prevent rendering the same item twice
 | ||||
|     const itemVisited = new Set([active]); | ||||
|     // Fill the page under the active item
 | ||||
|     let bufferPointer = activeItemPosition + activeItem.length; | ||||
|     let itemPointer = bound(active + 1); | ||||
|     while (bufferPointer < pageSize && | ||||
|         !itemVisited.has(itemPointer) && | ||||
|         (loop && renderedLength > pageSize ? itemPointer !== active : itemPointer > active)) { | ||||
|         const lines = renderItemAtIndex(itemPointer); | ||||
|         const linesToAdd = lines.slice(0, pageSize - bufferPointer); | ||||
|         pageBuffer.splice(bufferPointer, linesToAdd.length, ...linesToAdd); | ||||
|         // Move pointers for next iteration
 | ||||
|         itemVisited.add(itemPointer); | ||||
|         bufferPointer += linesToAdd.length; | ||||
|         itemPointer = bound(itemPointer + 1); | ||||
|     } | ||||
|     // Fill the page over the active item
 | ||||
|     bufferPointer = activeItemPosition - 1; | ||||
|     itemPointer = bound(active - 1); | ||||
|     while (bufferPointer >= 0 && | ||||
|         !itemVisited.has(itemPointer) && | ||||
|         (loop && renderedLength > pageSize ? itemPointer !== active : itemPointer < active)) { | ||||
|         const lines = renderItemAtIndex(itemPointer); | ||||
|         const linesToAdd = lines.slice(Math.max(0, lines.length - bufferPointer - 1)); | ||||
|         pageBuffer.splice(bufferPointer - linesToAdd.length + 1, linesToAdd.length, ...linesToAdd); | ||||
|         // Move pointers for next iteration
 | ||||
|         itemVisited.add(itemPointer); | ||||
|         bufferPointer -= linesToAdd.length; | ||||
|         itemPointer = bound(itemPointer - 1); | ||||
|     } | ||||
|     return pageBuffer.filter((line) => typeof line === 'string').join('\n'); | ||||
| } | ||||
| @ -0,0 +1,7 @@ | ||||
| export declare class PromisePolyfill<T> extends Promise<T> { | ||||
|     static withResolver<T>(): { | ||||
|         promise: Promise<T>; | ||||
|         resolve: (value: T) => void; | ||||
|         reject: (error: unknown) => void; | ||||
|     }; | ||||
| } | ||||
| @ -0,0 +1,18 @@ | ||||
| "use strict"; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.PromisePolyfill = void 0; | ||||
| // TODO: Remove this class once Node 22 becomes the minimum supported version.
 | ||||
| class PromisePolyfill extends Promise { | ||||
|     // Available starting from Node 22
 | ||||
|     // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers
 | ||||
|     static withResolver() { | ||||
|         let resolve; | ||||
|         let reject; | ||||
|         const promise = new Promise((res, rej) => { | ||||
|             resolve = res; | ||||
|             reject = rej; | ||||
|         }); | ||||
|         return { promise, resolve: resolve, reject: reject }; | ||||
|     } | ||||
| } | ||||
| exports.PromisePolyfill = PromisePolyfill; | ||||
| @ -0,0 +1,14 @@ | ||||
| import type { InquirerReadline } from '@inquirer/type'; | ||||
| export default class ScreenManager { | ||||
|     private height; | ||||
|     private extraLinesUnderPrompt; | ||||
|     private cursorPos; | ||||
|     private readonly rl; | ||||
|     constructor(rl: InquirerReadline); | ||||
|     write(content: string): void; | ||||
|     render(content: string, bottomContent?: string): void; | ||||
|     checkCursorPos(): void; | ||||
|     done({ clearContent }: { | ||||
|         clearContent: boolean; | ||||
|     }): void; | ||||
| } | ||||
| @ -0,0 +1,90 @@ | ||||
| "use strict"; | ||||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||||
|     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| const node_util_1 = require("node:util"); | ||||
| const ansi_escapes_1 = __importDefault(require("ansi-escapes")); | ||||
| const utils_ts_1 = require("./utils.js"); | ||||
| const height = (content) => content.split('\n').length; | ||||
| const lastLine = (content) => content.split('\n').pop() ?? ''; | ||||
| function cursorDown(n) { | ||||
|     return n > 0 ? ansi_escapes_1.default.cursorDown(n) : ''; | ||||
| } | ||||
| class ScreenManager { | ||||
|     // These variables are keeping information to allow correct prompt re-rendering
 | ||||
|     height = 0; | ||||
|     extraLinesUnderPrompt = 0; | ||||
|     cursorPos; | ||||
|     rl; | ||||
|     constructor(rl) { | ||||
|         this.rl = rl; | ||||
|         this.cursorPos = rl.getCursorPos(); | ||||
|     } | ||||
|     write(content) { | ||||
|         this.rl.output.unmute(); | ||||
|         this.rl.output.write(content); | ||||
|         this.rl.output.mute(); | ||||
|     } | ||||
|     render(content, bottomContent = '') { | ||||
|         // Write message to screen and setPrompt to control backspace
 | ||||
|         const promptLine = lastLine(content); | ||||
|         const rawPromptLine = (0, node_util_1.stripVTControlCharacters)(promptLine); | ||||
|         // Remove the rl.line from our prompt. We can't rely on the content of
 | ||||
|         // rl.line (mainly because of the password prompt), so just rely on it's
 | ||||
|         // length.
 | ||||
|         let prompt = rawPromptLine; | ||||
|         if (this.rl.line.length > 0) { | ||||
|             prompt = prompt.slice(0, -this.rl.line.length); | ||||
|         } | ||||
|         this.rl.setPrompt(prompt); | ||||
|         // SetPrompt will change cursor position, now we can get correct value
 | ||||
|         this.cursorPos = this.rl.getCursorPos(); | ||||
|         const width = (0, utils_ts_1.readlineWidth)(); | ||||
|         content = (0, utils_ts_1.breakLines)(content, width); | ||||
|         bottomContent = (0, utils_ts_1.breakLines)(bottomContent, width); | ||||
|         // Manually insert an extra line if we're at the end of the line.
 | ||||
|         // This prevent the cursor from appearing at the beginning of the
 | ||||
|         // current line.
 | ||||
|         if (rawPromptLine.length % width === 0) { | ||||
|             content += '\n'; | ||||
|         } | ||||
|         let output = content + (bottomContent ? '\n' + bottomContent : ''); | ||||
|         /** | ||||
|          * Re-adjust the cursor at the correct position. | ||||
|          */ | ||||
|         // We need to consider parts of the prompt under the cursor as part of the bottom
 | ||||
|         // content in order to correctly cleanup and re-render.
 | ||||
|         const promptLineUpDiff = Math.floor(rawPromptLine.length / width) - this.cursorPos.rows; | ||||
|         const bottomContentHeight = promptLineUpDiff + (bottomContent ? height(bottomContent) : 0); | ||||
|         // Return cursor to the input position (on top of the bottomContent)
 | ||||
|         if (bottomContentHeight > 0) | ||||
|             output += ansi_escapes_1.default.cursorUp(bottomContentHeight); | ||||
|         // Return cursor to the initial left offset.
 | ||||
|         output += ansi_escapes_1.default.cursorTo(this.cursorPos.cols); | ||||
|         /** | ||||
|          * Render and store state for future re-rendering | ||||
|          */ | ||||
|         this.write(cursorDown(this.extraLinesUnderPrompt) + | ||||
|             ansi_escapes_1.default.eraseLines(this.height) + | ||||
|             output); | ||||
|         this.extraLinesUnderPrompt = bottomContentHeight; | ||||
|         this.height = height(output); | ||||
|     } | ||||
|     checkCursorPos() { | ||||
|         const cursorPos = this.rl.getCursorPos(); | ||||
|         if (cursorPos.cols !== this.cursorPos.cols) { | ||||
|             this.write(ansi_escapes_1.default.cursorTo(cursorPos.cols)); | ||||
|             this.cursorPos = cursorPos; | ||||
|         } | ||||
|     } | ||||
|     done({ clearContent }) { | ||||
|         this.rl.setPrompt(''); | ||||
|         let output = cursorDown(this.extraLinesUnderPrompt); | ||||
|         output += clearContent ? ansi_escapes_1.default.eraseLines(this.height) : '\n'; | ||||
|         output += ansi_escapes_1.default.cursorShow; | ||||
|         this.write(output); | ||||
|         this.rl.close(); | ||||
|     } | ||||
| } | ||||
| exports.default = ScreenManager; | ||||
| @ -0,0 +1,155 @@ | ||||
| import type { Prettify } from '@inquirer/type'; | ||||
| /** | ||||
|  * Union type representing the possible statuses of a prompt. | ||||
|  * | ||||
|  * -   `'loading'`: The prompt is currently loading. | ||||
|  * -   `'idle'`: The prompt is loaded and currently waiting for the user to | ||||
|  *     submit an answer. | ||||
|  * -   `'done'`: The user has submitted an answer and the prompt is finished. | ||||
|  * -   `string`: Any other string: The prompt is in a custom state. | ||||
|  */ | ||||
| export type Status = 'loading' | 'idle' | 'done' | (string & {}); | ||||
| type DefaultTheme = { | ||||
|     /** | ||||
|      * Prefix to prepend to the message. If a function is provided, it will be | ||||
|      * called with the current status of the prompt, and the return value will be | ||||
|      * used as the prefix. | ||||
|      * | ||||
|      * @remarks | ||||
|      * If `status === 'loading'`, this property is ignored and the spinner (styled | ||||
|      * by the `spinner` property) will be displayed instead. | ||||
|      * | ||||
|      * @defaultValue | ||||
|      * ```ts
 | ||||
|      * // import colors from 'yoctocolors-cjs';
 | ||||
|      * (status) => status === 'done' ? colors.green('✔') : colors.blue('?') | ||||
|      * ``` | ||||
|      */ | ||||
|     prefix: string | Prettify<Omit<Record<Status, string>, 'loading'>>; | ||||
|     /** | ||||
|      * Configuration for the spinner that is displayed when the prompt is in the | ||||
|      * `'loading'` state. | ||||
|      * | ||||
|      * We recommend the use of {@link https://github.com/sindresorhus/cli-spinners|cli-spinners} for a list of available spinners.
 | ||||
|      */ | ||||
|     spinner: { | ||||
|         /** | ||||
|          * The time interval between frames, in milliseconds. | ||||
|          * | ||||
|          * @defaultValue | ||||
|          * ```ts
 | ||||
|          * 80 | ||||
|          * ``` | ||||
|          */ | ||||
|         interval: number; | ||||
|         /** | ||||
|          * A list of frames to show for the spinner. | ||||
|          * | ||||
|          * @defaultValue | ||||
|          * ```ts
 | ||||
|          * ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] | ||||
|          * ``` | ||||
|          */ | ||||
|         frames: string[]; | ||||
|     }; | ||||
|     /** | ||||
|      * Object containing functions to style different parts of the prompt. | ||||
|      */ | ||||
|     style: { | ||||
|         /** | ||||
|          * Style to apply to the user's answer once it has been submitted. | ||||
|          * | ||||
|          * @param text - The user's answer. | ||||
|          * @returns The styled answer. | ||||
|          * | ||||
|          * @defaultValue | ||||
|          * ```ts
 | ||||
|          * // import colors from 'yoctocolors-cjs';
 | ||||
|          * (text) => colors.cyan(text) | ||||
|          * ``` | ||||
|          */ | ||||
|         answer: (text: string) => string; | ||||
|         /** | ||||
|          * Style to apply to the message displayed to the user. | ||||
|          * | ||||
|          * @param text - The message to style. | ||||
|          * @param status - The current status of the prompt. | ||||
|          * @returns The styled message. | ||||
|          * | ||||
|          * @defaultValue | ||||
|          * ```ts
 | ||||
|          * // import colors from 'yoctocolors-cjs';
 | ||||
|          * (text, status) => colors.bold(text) | ||||
|          * ``` | ||||
|          */ | ||||
|         message: (text: string, status: Status) => string; | ||||
|         /** | ||||
|          * Style to apply to error messages. | ||||
|          * | ||||
|          * @param text - The error message. | ||||
|          * @returns The styled error message. | ||||
|          * | ||||
|          * @defaultValue | ||||
|          * ```ts
 | ||||
|          * // import colors from 'yoctocolors-cjs';
 | ||||
|          * (text) => colors.red(`> ${text}`) | ||||
|          * ``` | ||||
|          */ | ||||
|         error: (text: string) => string; | ||||
|         /** | ||||
|          * Style to apply to the default answer when one is provided. | ||||
|          * | ||||
|          * @param text - The default answer. | ||||
|          * @returns The styled default answer. | ||||
|          * | ||||
|          * @defaultValue | ||||
|          * ```ts
 | ||||
|          * // import colors from 'yoctocolors-cjs';
 | ||||
|          * (text) => colors.dim(`(${text})`) | ||||
|          * ``` | ||||
|          */ | ||||
|         defaultAnswer: (text: string) => string; | ||||
|         /** | ||||
|          * Style to apply to help text. | ||||
|          * | ||||
|          * @param text - The help text. | ||||
|          * @returns The styled help text. | ||||
|          * | ||||
|          * @defaultValue | ||||
|          * ```ts
 | ||||
|          * // import colors from 'yoctocolors-cjs';
 | ||||
|          * (text) => colors.dim(text) | ||||
|          * ``` | ||||
|          */ | ||||
|         help: (text: string) => string; | ||||
|         /** | ||||
|          * Style to apply to highlighted text. | ||||
|          * | ||||
|          * @param text - The text to highlight. | ||||
|          * @returns The highlighted text. | ||||
|          * | ||||
|          * @defaultValue | ||||
|          * ```ts
 | ||||
|          * // import colors from 'yoctocolors-cjs';
 | ||||
|          * (text) => colors.cyan(text) | ||||
|          * ``` | ||||
|          */ | ||||
|         highlight: (text: string) => string; | ||||
|         /** | ||||
|          * Style to apply to keyboard keys referred to in help texts. | ||||
|          * | ||||
|          * @param text - The key to style. | ||||
|          * @returns The styled key. | ||||
|          * | ||||
|          * @defaultValue | ||||
|          * ```ts
 | ||||
|          * // import colors from 'yoctocolors-cjs';
 | ||||
|          * (text) => colors.cyan(colors.bold(`<${text}>`)) | ||||
|          * ``` | ||||
|          */ | ||||
|         key: (text: string) => string; | ||||
|     }; | ||||
| }; | ||||
| export type Theme<Extension extends object = object> = Prettify<Extension & DefaultTheme>; | ||||
| export declare const defaultTheme: DefaultTheme; | ||||
| export {}; | ||||
| @ -0,0 +1,28 @@ | ||||
| "use strict"; | ||||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||||
|     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.defaultTheme = void 0; | ||||
| const yoctocolors_cjs_1 = __importDefault(require("yoctocolors-cjs")); | ||||
| const figures_1 = __importDefault(require("@inquirer/figures")); | ||||
| exports.defaultTheme = { | ||||
|     prefix: { | ||||
|         idle: yoctocolors_cjs_1.default.blue('?'), | ||||
|         // TODO: use figure
 | ||||
|         done: yoctocolors_cjs_1.default.green(figures_1.default.tick), | ||||
|     }, | ||||
|     spinner: { | ||||
|         interval: 80, | ||||
|         frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'].map((frame) => yoctocolors_cjs_1.default.yellow(frame)), | ||||
|     }, | ||||
|     style: { | ||||
|         answer: yoctocolors_cjs_1.default.cyan, | ||||
|         message: yoctocolors_cjs_1.default.bold, | ||||
|         error: (text) => yoctocolors_cjs_1.default.red(`> ${text}`), | ||||
|         defaultAnswer: (text) => yoctocolors_cjs_1.default.dim(`(${text})`), | ||||
|         help: yoctocolors_cjs_1.default.dim, | ||||
|         highlight: yoctocolors_cjs_1.default.cyan, | ||||
|         key: (text) => yoctocolors_cjs_1.default.cyan(yoctocolors_cjs_1.default.bold(`<${text}>`)), | ||||
|     }, | ||||
| }; | ||||
| @ -0,0 +1,2 @@ | ||||
| import type { InquirerReadline } from '@inquirer/type'; | ||||
| export declare function useEffect(cb: (rl: InquirerReadline) => void | (() => void), depArray: ReadonlyArray<unknown>): void; | ||||
| @ -0,0 +1,14 @@ | ||||
| "use strict"; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.useEffect = useEffect; | ||||
| const hook_engine_ts_1 = require("./hook-engine.js"); | ||||
| function useEffect(cb, depArray) { | ||||
|     (0, hook_engine_ts_1.withPointer)((pointer) => { | ||||
|         const oldDeps = pointer.get(); | ||||
|         const hasChanged = !Array.isArray(oldDeps) || depArray.some((dep, i) => !Object.is(dep, oldDeps[i])); | ||||
|         if (hasChanged) { | ||||
|             hook_engine_ts_1.effectScheduler.queue(cb); | ||||
|         } | ||||
|         pointer.set(depArray); | ||||
|     }); | ||||
| } | ||||
| @ -0,0 +1,3 @@ | ||||
| import { type InquirerReadline } from '@inquirer/type'; | ||||
| import { type KeypressEvent } from './key.ts'; | ||||
| export declare function useKeypress(userHandler: (event: KeypressEvent, rl: InquirerReadline) => void | Promise<void>): void; | ||||
| @ -0,0 +1,23 @@ | ||||
| "use strict"; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.useKeypress = useKeypress; | ||||
| const use_ref_ts_1 = require("./use-ref.js"); | ||||
| const use_effect_ts_1 = require("./use-effect.js"); | ||||
| const hook_engine_ts_1 = require("./hook-engine.js"); | ||||
| function useKeypress(userHandler) { | ||||
|     const signal = (0, use_ref_ts_1.useRef)(userHandler); | ||||
|     signal.current = userHandler; | ||||
|     (0, use_effect_ts_1.useEffect)((rl) => { | ||||
|         let ignore = false; | ||||
|         const handler = (0, hook_engine_ts_1.withUpdates)((_input, event) => { | ||||
|             if (ignore) | ||||
|                 return; | ||||
|             void signal.current(event, rl); | ||||
|         }); | ||||
|         rl.input.on('keypress', handler); | ||||
|         return () => { | ||||
|             ignore = true; | ||||
|             rl.input.removeListener('keypress', handler); | ||||
|         }; | ||||
|     }, []); | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| export declare function useMemo<Value>(fn: () => Value, dependencies: ReadonlyArray<unknown>): Value; | ||||
| @ -0,0 +1,17 @@ | ||||
| "use strict"; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.useMemo = useMemo; | ||||
| const hook_engine_ts_1 = require("./hook-engine.js"); | ||||
| function useMemo(fn, dependencies) { | ||||
|     return (0, hook_engine_ts_1.withPointer)((pointer) => { | ||||
|         const prev = pointer.get(); | ||||
|         if (!prev || | ||||
|             prev.dependencies.length !== dependencies.length || | ||||
|             prev.dependencies.some((dep, i) => dep !== dependencies[i])) { | ||||
|             const value = fn(); | ||||
|             pointer.set({ value, dependencies }); | ||||
|             return value; | ||||
|         } | ||||
|         return prev.value; | ||||
|     }); | ||||
| } | ||||
| @ -0,0 +1,5 @@ | ||||
| import type { Theme, Status } from './theme.ts'; | ||||
| export declare function usePrefix({ status, theme, }: { | ||||
|     status?: Status; | ||||
|     theme?: Theme; | ||||
| }): string; | ||||
| @ -0,0 +1,38 @@ | ||||
| "use strict"; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.usePrefix = usePrefix; | ||||
| const use_state_ts_1 = require("./use-state.js"); | ||||
| const use_effect_ts_1 = require("./use-effect.js"); | ||||
| const make_theme_ts_1 = require("./make-theme.js"); | ||||
| function usePrefix({ status = 'idle', theme, }) { | ||||
|     const [showLoader, setShowLoader] = (0, use_state_ts_1.useState)(false); | ||||
|     const [tick, setTick] = (0, use_state_ts_1.useState)(0); | ||||
|     const { prefix, spinner } = (0, make_theme_ts_1.makeTheme)(theme); | ||||
|     (0, use_effect_ts_1.useEffect)(() => { | ||||
|         if (status === 'loading') { | ||||
|             let tickInterval; | ||||
|             let inc = -1; | ||||
|             // Delay displaying spinner by 300ms, to avoid flickering
 | ||||
|             const delayTimeout = setTimeout(() => { | ||||
|                 setShowLoader(true); | ||||
|                 tickInterval = setInterval(() => { | ||||
|                     inc = inc + 1; | ||||
|                     setTick(inc % spinner.frames.length); | ||||
|                 }, spinner.interval); | ||||
|             }, 300); | ||||
|             return () => { | ||||
|                 clearTimeout(delayTimeout); | ||||
|                 clearInterval(tickInterval); | ||||
|             }; | ||||
|         } | ||||
|         else { | ||||
|             setShowLoader(false); | ||||
|         } | ||||
|     }, [status]); | ||||
|     if (showLoader) { | ||||
|         return spinner.frames[tick]; | ||||
|     } | ||||
|     // There's a delay before we show the loader. So we want to ignore `loading` here, and pass idle instead.
 | ||||
|     const iconName = status === 'loading' ? 'idle' : status; | ||||
|     return typeof prefix === 'string' ? prefix : (prefix[iconName] ?? prefix['idle']); | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| export declare function useRef<Value>(val: Value): { | ||||
|     current: Value; | ||||
| }; | ||||
| export declare function useRef<Value>(val?: Value): { | ||||
|     current: Value | undefined; | ||||
| }; | ||||
| @ -0,0 +1,7 @@ | ||||
| "use strict"; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.useRef = useRef; | ||||
| const use_state_ts_1 = require("./use-state.js"); | ||||
| function useRef(val) { | ||||
|     return (0, use_state_ts_1.useState)({ current: val })[0]; | ||||
| } | ||||
| @ -0,0 +1,4 @@ | ||||
| type NotFunction<T> = T extends (...args: never) => unknown ? never : T; | ||||
| export declare function useState<Value>(defaultValue: NotFunction<Value> | (() => Value)): [Value, (newValue: Value) => void]; | ||||
| export declare function useState<Value>(defaultValue?: NotFunction<Value> | (() => Value)): [Value | undefined, (newValue?: Value) => void]; | ||||
| export {}; | ||||
| @ -0,0 +1,23 @@ | ||||
| "use strict"; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.useState = useState; | ||||
| const node_async_hooks_1 = require("node:async_hooks"); | ||||
| const hook_engine_ts_1 = require("./hook-engine.js"); | ||||
| function useState(defaultValue) { | ||||
|     return (0, hook_engine_ts_1.withPointer)((pointer) => { | ||||
|         const setState = node_async_hooks_1.AsyncResource.bind(function setState(newValue) { | ||||
|             // Noop if the value is still the same.
 | ||||
|             if (pointer.get() !== newValue) { | ||||
|                 pointer.set(newValue); | ||||
|                 // Trigger re-render
 | ||||
|                 (0, hook_engine_ts_1.handleChange)(); | ||||
|             } | ||||
|         }); | ||||
|         if (pointer.initialized) { | ||||
|             return [pointer.get(), setState]; | ||||
|         } | ||||
|         const value = typeof defaultValue === 'function' ? defaultValue() : defaultValue; | ||||
|         pointer.set(value); | ||||
|         return [value, setState]; | ||||
|     }); | ||||
| } | ||||
| @ -0,0 +1,13 @@ | ||||
| /** | ||||
|  * Force line returns at specific width. This function is ANSI code friendly and it'll | ||||
|  * ignore invisible codes during width calculation. | ||||
|  * @param {string} content | ||||
|  * @param {number} width | ||||
|  * @return {string} | ||||
|  */ | ||||
| export declare function breakLines(content: string, width: number): string; | ||||
| /** | ||||
|  * Returns the width of the active readline, or 80 as default value. | ||||
|  * @returns {number} | ||||
|  */ | ||||
| export declare function readlineWidth(): number; | ||||
| @ -0,0 +1,32 @@ | ||||
| "use strict"; | ||||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||||
|     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.breakLines = breakLines; | ||||
| exports.readlineWidth = readlineWidth; | ||||
| const cli_width_1 = __importDefault(require("cli-width")); | ||||
| const wrap_ansi_1 = __importDefault(require("wrap-ansi")); | ||||
| const hook_engine_ts_1 = require("./hook-engine.js"); | ||||
| /** | ||||
|  * Force line returns at specific width. This function is ANSI code friendly and it'll | ||||
|  * ignore invisible codes during width calculation. | ||||
|  * @param {string} content | ||||
|  * @param {number} width | ||||
|  * @return {string} | ||||
|  */ | ||||
| function breakLines(content, width) { | ||||
|     return content | ||||
|         .split('\n') | ||||
|         .flatMap((line) => (0, wrap_ansi_1.default)(line, width, { trim: false, hard: true }) | ||||
|         .split('\n') | ||||
|         .map((str) => str.trimEnd())) | ||||
|         .join('\n'); | ||||
| } | ||||
| /** | ||||
|  * Returns the width of the active readline, or 80 as default value. | ||||
|  * @returns {number} | ||||
|  */ | ||||
| function readlineWidth() { | ||||
|     return (0, cli_width_1.default)({ defaultWidth: 80, output: (0, hook_engine_ts_1.readline)().output }); | ||||
| } | ||||
| @ -0,0 +1,3 @@ | ||||
| { | ||||
|   "type": "commonjs" | ||||
| } | ||||
| @ -0,0 +1,13 @@ | ||||
| export * from './lib/key.ts'; | ||||
| export * from './lib/errors.ts'; | ||||
| export { usePrefix } from './lib/use-prefix.ts'; | ||||
| export { useState } from './lib/use-state.ts'; | ||||
| export { useEffect } from './lib/use-effect.ts'; | ||||
| export { useMemo } from './lib/use-memo.ts'; | ||||
| export { useRef } from './lib/use-ref.ts'; | ||||
| export { useKeypress } from './lib/use-keypress.ts'; | ||||
| export { makeTheme } from './lib/make-theme.ts'; | ||||
| export type { Theme, Status } from './lib/theme.ts'; | ||||
| export { usePagination } from './lib/pagination/use-pagination.ts'; | ||||
| export { createPrompt } from './lib/create-prompt.ts'; | ||||
| export { Separator } from './lib/Separator.ts'; | ||||
| @ -0,0 +1,12 @@ | ||||
| export * from "./lib/key.js"; | ||||
| export * from "./lib/errors.js"; | ||||
| export { usePrefix } from "./lib/use-prefix.js"; | ||||
| export { useState } from "./lib/use-state.js"; | ||||
| export { useEffect } from "./lib/use-effect.js"; | ||||
| export { useMemo } from "./lib/use-memo.js"; | ||||
| export { useRef } from "./lib/use-ref.js"; | ||||
| export { useKeypress } from "./lib/use-keypress.js"; | ||||
| export { makeTheme } from "./lib/make-theme.js"; | ||||
| export { usePagination } from "./lib/pagination/use-pagination.js"; | ||||
| export { createPrompt } from "./lib/create-prompt.js"; | ||||
| export { Separator } from "./lib/Separator.js"; | ||||
| @ -0,0 +1,10 @@ | ||||
| /** | ||||
|  * Separator object | ||||
|  * Used to space/separate choices group | ||||
|  */ | ||||
| export declare class Separator { | ||||
|     readonly separator: string; | ||||
|     readonly type: string; | ||||
|     constructor(separator?: string); | ||||
|     static isSeparator(choice: unknown): choice is Separator; | ||||
| } | ||||
| @ -0,0 +1,21 @@ | ||||
| import colors from 'yoctocolors-cjs'; | ||||
| import figures from '@inquirer/figures'; | ||||
| /** | ||||
|  * Separator object | ||||
|  * Used to space/separate choices group | ||||
|  */ | ||||
| export class Separator { | ||||
|     separator = colors.dim(Array.from({ length: 15 }).join(figures.line)); | ||||
|     type = 'separator'; | ||||
|     constructor(separator) { | ||||
|         if (separator) { | ||||
|             this.separator = separator; | ||||
|         } | ||||
|     } | ||||
|     static isSeparator(choice) { | ||||
|         return Boolean(choice && | ||||
|             typeof choice === 'object' && | ||||
|             'type' in choice && | ||||
|             choice.type === 'separator'); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,4 @@ | ||||
| import { type Prompt, type Prettify } from '@inquirer/type'; | ||||
| type ViewFunction<Value, Config> = (config: Prettify<Config>, done: (value: Value) => void) => string | [string, string | undefined]; | ||||
| export declare function createPrompt<Value, Config>(view: ViewFunction<Value, Config>): Prompt<Value, Config>; | ||||
| export {}; | ||||
| @ -0,0 +1,117 @@ | ||||
| import * as readline from 'node:readline'; | ||||
| import { AsyncResource } from 'node:async_hooks'; | ||||
| import MuteStream from 'mute-stream'; | ||||
| import { onExit as onSignalExit } from 'signal-exit'; | ||||
| import ScreenManager from "./screen-manager.js"; | ||||
| import { PromisePolyfill } from "./promise-polyfill.js"; | ||||
| import { withHooks, effectScheduler } from "./hook-engine.js"; | ||||
| import { AbortPromptError, CancelPromptError, ExitPromptError } from "./errors.js"; | ||||
| function getCallSites() { | ||||
|     // eslint-disable-next-line @typescript-eslint/unbound-method
 | ||||
|     const _prepareStackTrace = Error.prepareStackTrace; | ||||
|     let result = []; | ||||
|     try { | ||||
|         Error.prepareStackTrace = (_, callSites) => { | ||||
|             const callSitesWithoutCurrent = callSites.slice(1); | ||||
|             result = callSitesWithoutCurrent; | ||||
|             return callSitesWithoutCurrent; | ||||
|         }; | ||||
|         // eslint-disable-next-line @typescript-eslint/no-unused-expressions
 | ||||
|         new Error().stack; | ||||
|     } | ||||
|     catch { | ||||
|         // An error will occur if the Node flag --frozen-intrinsics is used.
 | ||||
|         // https://nodejs.org/api/cli.html#--frozen-intrinsics
 | ||||
|         return result; | ||||
|     } | ||||
|     Error.prepareStackTrace = _prepareStackTrace; | ||||
|     return result; | ||||
| } | ||||
| export function createPrompt(view) { | ||||
|     const callSites = getCallSites(); | ||||
|     const prompt = (config, context = {}) => { | ||||
|         // Default `input` to stdin
 | ||||
|         const { input = process.stdin, signal } = context; | ||||
|         const cleanups = new Set(); | ||||
|         // Add mute capabilities to the output
 | ||||
|         const output = new MuteStream(); | ||||
|         output.pipe(context.output ?? process.stdout); | ||||
|         const rl = readline.createInterface({ | ||||
|             terminal: true, | ||||
|             input, | ||||
|             output, | ||||
|         }); | ||||
|         const screen = new ScreenManager(rl); | ||||
|         const { promise, resolve, reject } = PromisePolyfill.withResolver(); | ||||
|         const cancel = () => reject(new CancelPromptError()); | ||||
|         if (signal) { | ||||
|             const abort = () => reject(new AbortPromptError({ cause: signal.reason })); | ||||
|             if (signal.aborted) { | ||||
|                 abort(); | ||||
|                 return Object.assign(promise, { cancel }); | ||||
|             } | ||||
|             signal.addEventListener('abort', abort); | ||||
|             cleanups.add(() => signal.removeEventListener('abort', abort)); | ||||
|         } | ||||
|         cleanups.add(onSignalExit((code, signal) => { | ||||
|             reject(new ExitPromptError(`User force closed the prompt with ${code} ${signal}`)); | ||||
|         })); | ||||
|         // SIGINT must be explicitly handled by the prompt so the ExitPromptError can be handled.
 | ||||
|         // Otherwise, the prompt will stop and in some scenarios never resolve.
 | ||||
|         // Ref issue #1741
 | ||||
|         const sigint = () => reject(new ExitPromptError(`User force closed the prompt with SIGINT`)); | ||||
|         rl.on('SIGINT', sigint); | ||||
|         cleanups.add(() => rl.removeListener('SIGINT', sigint)); | ||||
|         // Re-renders only happen when the state change; but the readline cursor could change position
 | ||||
|         // and that also requires a re-render (and a manual one because we mute the streams).
 | ||||
|         // We set the listener after the initial workLoop to avoid a double render if render triggered
 | ||||
|         // by a state change sets the cursor to the right position.
 | ||||
|         const checkCursorPos = () => screen.checkCursorPos(); | ||||
|         rl.input.on('keypress', checkCursorPos); | ||||
|         cleanups.add(() => rl.input.removeListener('keypress', checkCursorPos)); | ||||
|         return withHooks(rl, (cycle) => { | ||||
|             // The close event triggers immediately when the user press ctrl+c. SignalExit on the other hand
 | ||||
|             // triggers after the process is done (which happens after timeouts are done triggering.)
 | ||||
|             // We triggers the hooks cleanup phase on rl `close` so active timeouts can be cleared.
 | ||||
|             const hooksCleanup = AsyncResource.bind(() => effectScheduler.clearAll()); | ||||
|             rl.on('close', hooksCleanup); | ||||
|             cleanups.add(() => rl.removeListener('close', hooksCleanup)); | ||||
|             cycle(() => { | ||||
|                 try { | ||||
|                     const nextView = view(config, (value) => { | ||||
|                         setImmediate(() => resolve(value)); | ||||
|                     }); | ||||
|                     // Typescript won't allow this, but not all users rely on typescript.
 | ||||
|                     // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | ||||
|                     if (nextView === undefined) { | ||||
|                         const callerFilename = callSites[1]?.getFileName(); | ||||
|                         throw new Error(`Prompt functions must return a string.\n    at ${callerFilename}`); | ||||
|                     } | ||||
|                     const [content, bottomContent] = typeof nextView === 'string' ? [nextView] : nextView; | ||||
|                     screen.render(content, bottomContent); | ||||
|                     effectScheduler.run(); | ||||
|                 } | ||||
|                 catch (error) { | ||||
|                     reject(error); | ||||
|                 } | ||||
|             }); | ||||
|             return Object.assign(promise | ||||
|                 .then((answer) => { | ||||
|                 effectScheduler.clearAll(); | ||||
|                 return answer; | ||||
|             }, (error) => { | ||||
|                 effectScheduler.clearAll(); | ||||
|                 throw error; | ||||
|             }) | ||||
|                 // Wait for the promise to settle, then cleanup.
 | ||||
|                 .finally(() => { | ||||
|                 cleanups.forEach((cleanup) => cleanup()); | ||||
|                 screen.done({ clearContent: Boolean(context.clearPromptOnDone) }); | ||||
|                 output.end(); | ||||
|             }) | ||||
|                 // Once cleanup is done, let the expose promise resolve/reject to the internal one.
 | ||||
|                 .then(() => promise), { cancel }); | ||||
|         }); | ||||
|     }; | ||||
|     return prompt; | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| export declare class AbortPromptError extends Error { | ||||
|     name: string; | ||||
|     message: string; | ||||
|     constructor(options?: { | ||||
|         cause?: unknown; | ||||
|     }); | ||||
| } | ||||
| export declare class CancelPromptError extends Error { | ||||
|     name: string; | ||||
|     message: string; | ||||
| } | ||||
| export declare class ExitPromptError extends Error { | ||||
|     name: string; | ||||
| } | ||||
| export declare class HookError extends Error { | ||||
|     name: string; | ||||
| } | ||||
| export declare class ValidationError extends Error { | ||||
|     name: string; | ||||
| } | ||||
| @ -0,0 +1,21 @@ | ||||
| export class AbortPromptError extends Error { | ||||
|     name = 'AbortPromptError'; | ||||
|     message = 'Prompt was aborted'; | ||||
|     constructor(options) { | ||||
|         super(); | ||||
|         this.cause = options?.cause; | ||||
|     } | ||||
| } | ||||
| export class CancelPromptError extends Error { | ||||
|     name = 'CancelPromptError'; | ||||
|     message = 'Prompt was canceled'; | ||||
| } | ||||
| export class ExitPromptError extends Error { | ||||
|     name = 'ExitPromptError'; | ||||
| } | ||||
| export class HookError extends Error { | ||||
|     name = 'HookError'; | ||||
| } | ||||
| export class ValidationError extends Error { | ||||
|     name = 'ValidationError'; | ||||
| } | ||||
| @ -0,0 +1,23 @@ | ||||
| import type { InquirerReadline } from '@inquirer/type'; | ||||
| export declare function withHooks<T>(rl: InquirerReadline, cb: (cycle: (render: () => void) => void) => T): T; | ||||
| export declare function readline(): InquirerReadline; | ||||
| export declare function withUpdates<Args extends unknown[], R>(fn: (...args: Args) => R): (...args: Args) => R; | ||||
| type SetPointer<Value> = { | ||||
|     get(): Value; | ||||
|     set(value: Value): void; | ||||
|     initialized: true; | ||||
| }; | ||||
| type UnsetPointer<Value> = { | ||||
|     get(): void; | ||||
|     set(value: Value): void; | ||||
|     initialized: false; | ||||
| }; | ||||
| type Pointer<Value> = SetPointer<Value> | UnsetPointer<Value>; | ||||
| export declare function withPointer<Value, ReturnValue>(cb: (pointer: Pointer<Value>) => ReturnValue): ReturnValue; | ||||
| export declare function handleChange(): void; | ||||
| export declare const effectScheduler: { | ||||
|     queue(cb: (readline: InquirerReadline) => void | (() => void)): void; | ||||
|     run(): void; | ||||
|     clearAll(): void; | ||||
| }; | ||||
| export {}; | ||||
| @ -0,0 +1,110 @@ | ||||
| /* eslint @typescript-eslint/no-explicit-any: ["off"] */ | ||||
| import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks'; | ||||
| import { HookError, ValidationError } from "./errors.js"; | ||||
| const hookStorage = new AsyncLocalStorage(); | ||||
| function createStore(rl) { | ||||
|     const store = { | ||||
|         rl, | ||||
|         hooks: [], | ||||
|         hooksCleanup: [], | ||||
|         hooksEffect: [], | ||||
|         index: 0, | ||||
|         handleChange() { }, | ||||
|     }; | ||||
|     return store; | ||||
| } | ||||
| // Run callback in with the hook engine setup.
 | ||||
| export function withHooks(rl, cb) { | ||||
|     const store = createStore(rl); | ||||
|     return hookStorage.run(store, () => { | ||||
|         function cycle(render) { | ||||
|             store.handleChange = () => { | ||||
|                 store.index = 0; | ||||
|                 render(); | ||||
|             }; | ||||
|             store.handleChange(); | ||||
|         } | ||||
|         return cb(cycle); | ||||
|     }); | ||||
| } | ||||
| // Safe getStore utility that'll return the store or throw if undefined.
 | ||||
| function getStore() { | ||||
|     const store = hookStorage.getStore(); | ||||
|     if (!store) { | ||||
|         throw new HookError('[Inquirer] Hook functions can only be called from within a prompt'); | ||||
|     } | ||||
|     return store; | ||||
| } | ||||
| export function readline() { | ||||
|     return getStore().rl; | ||||
| } | ||||
| // Merge state updates happening within the callback function to avoid multiple renders.
 | ||||
| export function withUpdates(fn) { | ||||
|     const wrapped = (...args) => { | ||||
|         const store = getStore(); | ||||
|         let shouldUpdate = false; | ||||
|         const oldHandleChange = store.handleChange; | ||||
|         store.handleChange = () => { | ||||
|             shouldUpdate = true; | ||||
|         }; | ||||
|         const returnValue = fn(...args); | ||||
|         if (shouldUpdate) { | ||||
|             oldHandleChange(); | ||||
|         } | ||||
|         store.handleChange = oldHandleChange; | ||||
|         return returnValue; | ||||
|     }; | ||||
|     return AsyncResource.bind(wrapped); | ||||
| } | ||||
| export function withPointer(cb) { | ||||
|     const store = getStore(); | ||||
|     const { index } = store; | ||||
|     const pointer = { | ||||
|         get() { | ||||
|             return store.hooks[index]; | ||||
|         }, | ||||
|         set(value) { | ||||
|             store.hooks[index] = value; | ||||
|         }, | ||||
|         initialized: index in store.hooks, | ||||
|     }; | ||||
|     const returnValue = cb(pointer); | ||||
|     store.index++; | ||||
|     return returnValue; | ||||
| } | ||||
| export function handleChange() { | ||||
|     getStore().handleChange(); | ||||
| } | ||||
| export const effectScheduler = { | ||||
|     queue(cb) { | ||||
|         const store = getStore(); | ||||
|         const { index } = store; | ||||
|         store.hooksEffect.push(() => { | ||||
|             store.hooksCleanup[index]?.(); | ||||
|             const cleanFn = cb(readline()); | ||||
|             if (cleanFn != null && typeof cleanFn !== 'function') { | ||||
|                 throw new ValidationError('useEffect return value must be a cleanup function or nothing.'); | ||||
|             } | ||||
|             store.hooksCleanup[index] = cleanFn; | ||||
|         }); | ||||
|     }, | ||||
|     run() { | ||||
|         const store = getStore(); | ||||
|         withUpdates(() => { | ||||
|             store.hooksEffect.forEach((effect) => { | ||||
|                 effect(); | ||||
|             }); | ||||
|             // Warning: Clean the hooks before exiting the `withUpdates` block.
 | ||||
|             // Failure to do so means an updates would hit the same effects again.
 | ||||
|             store.hooksEffect.length = 0; | ||||
|         })(); | ||||
|     }, | ||||
|     clearAll() { | ||||
|         const store = getStore(); | ||||
|         store.hooksCleanup.forEach((cleanFn) => { | ||||
|             cleanFn?.(); | ||||
|         }); | ||||
|         store.hooksEffect.length = 0; | ||||
|         store.hooksCleanup.length = 0; | ||||
|     }, | ||||
| }; | ||||
| @ -0,0 +1,10 @@ | ||||
| export type KeypressEvent = { | ||||
|     name: string; | ||||
|     ctrl: boolean; | ||||
| }; | ||||
| export declare const isUpKey: (key: KeypressEvent) => boolean; | ||||
| export declare const isDownKey: (key: KeypressEvent) => boolean; | ||||
| export declare const isSpaceKey: (key: KeypressEvent) => boolean; | ||||
| export declare const isBackspaceKey: (key: KeypressEvent) => boolean; | ||||
| export declare const isNumberKey: (key: KeypressEvent) => boolean; | ||||
| export declare const isEnterKey: (key: KeypressEvent) => boolean; | ||||
| @ -0,0 +1,18 @@ | ||||
| export const isUpKey = (key) =>  | ||||
| // The up key
 | ||||
| key.name === 'up' || | ||||
|     // Vim keybinding
 | ||||
|     key.name === 'k' || | ||||
|     // Emacs keybinding
 | ||||
|     (key.ctrl && key.name === 'p'); | ||||
| export const isDownKey = (key) =>  | ||||
| // The down key
 | ||||
| key.name === 'down' || | ||||
|     // Vim keybinding
 | ||||
|     key.name === 'j' || | ||||
|     // Emacs keybinding
 | ||||
|     (key.ctrl && key.name === 'n'); | ||||
| export const isSpaceKey = (key) => key.name === 'space'; | ||||
| export const isBackspaceKey = (key) => key.name === 'backspace'; | ||||
| export const isNumberKey = (key) => '1234567890'.includes(key.name); | ||||
| export const isEnterKey = (key) => key.name === 'enter' || key.name === 'return'; | ||||
| @ -0,0 +1,3 @@ | ||||
| import type { Prettify, PartialDeep } from '@inquirer/type'; | ||||
| import { type Theme } from './theme.ts'; | ||||
| export declare function makeTheme<SpecificTheme extends object>(...themes: ReadonlyArray<undefined | PartialDeep<Theme<SpecificTheme>>>): Prettify<Theme<SpecificTheme>>; | ||||
| @ -0,0 +1,30 @@ | ||||
| import { defaultTheme } from "./theme.js"; | ||||
| function isPlainObject(value) { | ||||
|     if (typeof value !== 'object' || value === null) | ||||
|         return false; | ||||
|     let proto = value; | ||||
|     while (Object.getPrototypeOf(proto) !== null) { | ||||
|         proto = Object.getPrototypeOf(proto); | ||||
|     } | ||||
|     return Object.getPrototypeOf(value) === proto; | ||||
| } | ||||
| function deepMerge(...objects) { | ||||
|     const output = {}; | ||||
|     for (const obj of objects) { | ||||
|         for (const [key, value] of Object.entries(obj)) { | ||||
|             const prevValue = output[key]; | ||||
|             output[key] = | ||||
|                 isPlainObject(prevValue) && isPlainObject(value) | ||||
|                     ? deepMerge(prevValue, value) | ||||
|                     : value; | ||||
|         } | ||||
|     } | ||||
|     return output; | ||||
| } | ||||
| export function makeTheme(...themes) { | ||||
|     const themesToMerge = [ | ||||
|         defaultTheme, | ||||
|         ...themes.filter((theme) => theme != null), | ||||
|     ]; | ||||
|     return deepMerge(...themesToMerge); | ||||
| } | ||||
| @ -0,0 +1,16 @@ | ||||
| import type { Prettify } from '@inquirer/type'; | ||||
| export declare function usePagination<T>({ items, active, renderItem, pageSize, loop, }: { | ||||
|     items: ReadonlyArray<T>; | ||||
|     /** The index of the active item. */ | ||||
|     active: number; | ||||
|     /** Renders an item as part of a page. */ | ||||
|     renderItem: (layout: Prettify<{ | ||||
|         item: T; | ||||
|         index: number; | ||||
|         isActive: boolean; | ||||
|     }>) => string; | ||||
|     /** The size of the page. */ | ||||
|     pageSize: number; | ||||
|     /** Allows creating an infinitely looping list. `true` if unspecified. */ | ||||
|     loop?: boolean; | ||||
| }): string; | ||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in New Issue