From 41e0129bc6ea02ff8bbbd4b986b265c5efcf3d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Zorro?= Date: Tue, 16 Jul 2024 11:01:43 -0500 Subject: [PATCH 1/9] refactor(generators): implemented inquirer@10 for workspace generator --- bun.lockb | Bin 108120 -> 114840 bytes lib/generators/index.ts | 1 + lib/generators/workspace.ts | 89 ++++++++++++++---------------- lib/utils/generator.ts | 49 ++++++++++++---- lib/utils/questions/validators.ts | 2 +- package.json | 2 + 6 files changed, 85 insertions(+), 58 deletions(-) diff --git a/bun.lockb b/bun.lockb index dfa2e8f597ba0381d5fa727f54f8d3a5a24cf825..cde599f633ec348ba55e387c69ad74a532dd622e 100755 GIT binary patch delta 23438 zcmeHvXINE7*Y=(R2nWT6NR=j{sC3~_51?W@Vn-BXK{yIZ6R@C(2W#wxjJg#?MPu*1 z#ol`~F}4sxo~Y4SVo9R0z4zKhdGhG{zWKiI&v#wf7x!6n&#WnH&6?T!6z0mADs#tJ ze&**B^Jk3*~AThdToB_Gp^sU`_*0zd+Hy*t%cPACeRg7oaEN)*QPR& za)hdaP*j+bH!vTxD+;O+4-Ctu($)$O(g=bz@<%Ar8PXoSCM4AlgZBo$tPw4p-In0)oP*d*wE}Eq34-`H;CogSy zUP|sy;FNz|$-e=;%ELGZPJZ;T6$GdhWrI^Mx!@#kV>qN=12$2)IVABbkSH=0J%(a! zWYmPDhF*gZe}EUNa08Nte_rAH6}cXg^cO(lujsHcu)>tAOeBTNm~HAPHzTbe&uA3R zf|DU74zj`h^HNfc`6e{tC>JC_(uh|<(sX2IpLB z0ZuH+$u<^bWEq95jI4}Q-vQZIkx%t=Qwq|32OA5|D*Om!C^fJH3F>eiB)Mn_B)M*Q zdPZtG8qUZ^7m)ou<*5j%&#lc}O?#C7(}(5{E=2JrfM#O9mpl%&ynp6UWB-i2wA7IU zp&_d{wj#1FQ>a?Jo$J{i4qy?I5iJWc}yh6x$OaX@>HRS;LHZf{O z;SELR`ex>&rW6<<5gY|Vu5UpeO`SSWbpXplWJ3y5GHDqIYWt-yhzuQ!_D#My1B7*< z^2k&2@-haXrI}Em5f(v`Wmze?zB&B|3B$n2f^?;PZJ6AFTE0}_J(YT@0UsK8QG{TU z=6*ItPp&hMl!I;}ICZ=boCc!qHySUAzOS?;;(nQ%l9z9cK>;<8 z)LNG9ASr}yLXu&FG72(ail8>Mq>Y?k3k_qZdt0wpTiMPwkhJTBDLf-vsxFkbpqR%Z zNf|VZ+7pK6W%v%t7lhoQ*@dY&X~sSVxnes=vLq6c9QYg+u}2r_W91I=5@bi;h9tdj zAgRMckhI;HW9n%2KJ6gu|D?!oNSaV^M9D}|8v3r2Y)RkFvIo{cQpbsq6e0!bc{xJ| zrVCMBWXtzK-xc`=)Th#$nCt zuRj|wvG}g@Z+nLp`zKHL-WDCM&51g8w{yv!4y`9XwRf%FCHcXeYMR_u= zSX8Ty{&=}-q*jkqQ>katEd(P6nsEy;`=o@K*$PtO>O&B%-3 z^_(NwJ$}_QM%x%mjGSS?(V6xmFq&Y=v|52!|JZhm5NraXO0ed|wHxxUYFlYXqlAKo zagRiucBjI$JjFq$b*od^*}cIis?BJ2Yb^w$dJwdx)fPRptPXdI*J~SK562EmQ|YR! zH3*FAQvX;SLUpzOT5C{({p&wVEFPdlFS)TP_5@ln2t0RPt#mNemVa++#Rp4np+p+{ zfArrF`-|HDzj;RUu{}?&uebOS3IV)#XcTkc*Xobf_Q6g?gFyt=(`lAE@DeY*wj7zw zkcm}{aB#-nMJfnHEKegC1%bTYH-pKGwIVRg3WTazkzv~QPI8e}TKyBjqM zUIO9XfL{yHYkM_Nt5?+4X%;o$$$@%p1v2qCfw7RUe7xmplmbL^-J3fF=`}75d2*0m zo77Mp3pRZ1JTN&PX`Hvf$Y1g}1MseZY?n4Ri%hWQ(qyeiiXsMqfEMn7k@v8E5nJ9q z^5EccC!N&BjUSZn(Ao3ieNE6YRJB8>i#y7^BA71JeslA1iymO^E!h$Ap9|8rB zW?&QU6sot_gUldaT0c_Du%*gNr7S?FjR%t*N=AGFrUX0o`mNE}kyd+$&O$m5A-x$yeFxii^-X4LGkFl;XVF7`>BtoxU7$^t>kST4{T64sa;%e*_ zu|YgJQm>tWOsXf{nzhHknoAXM3)5HybEhc1wnMNW^paIPb=p;6pQy)1h9R*38bd9%s`Losbu*c�(bURI?haU} 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 b*fDbod*fs+9FUif)dBiW)I&V5jLrQo+C6u3 delta 19072 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 diff --git a/lib/generators/index.ts b/lib/generators/index.ts index 270c746..2312550 100644 --- a/lib/generators/index.ts +++ b/lib/generators/index.ts @@ -6,6 +6,7 @@ export { default as viewGenerator } from "./view"; export { default as systemGenerator } from "./system"; export { default as containerGenerator } from "./container"; export { default as relationshipGenerator } from "./relationship"; + // TODO: componentGenerator // - Available when the system finds containers // - Select from a list of containers diff --git a/lib/generators/workspace.ts b/lib/generators/workspace.ts index f109403..49d55bf 100644 --- a/lib/generators/workspace.ts +++ b/lib/generators/workspace.ts @@ -1,59 +1,54 @@ +import { confirm, input } from "@inquirer/prompts"; import { $ } from "bun"; -import type { Answers } from "inquirer"; import type { AddAction, AddManyAction } from "../utils/actions"; -import type { GeneratorDefinition } from "../utils/generator"; +import type { GeneratorDefinition, QuestionsObject } from "../utils/generator"; import { stringEmpty } from "../utils/questions/validators"; const globalUserName = await $`git config --global user.name`.text(); const globalUserEmail = await $`git config --global user.email`.text(); -const generator: GeneratorDefinition = { +const generator: GeneratorDefinition = { name: "Workspace", description: "Create a new workspace", - questions: [ - { - type: "input", - name: "workspaceName", - message: "Workspace name:", - validate: stringEmpty, - }, - { - type: "input", - name: "workspaceDescription", - message: "Workspace description:", - default: "Untitled Workspace", - }, - { - type: "input", - name: "systemName", - message: "System name:", - validate: stringEmpty, - }, - { - type: "input", - name: "systemDescription", - message: "System description:", - default: "Untitled System", - }, - { - type: "input", - name: "authorName", - message: "Author Name:", - default: globalUserName.trim(), - }, - { - type: "input", - name: "authorEmail", - message: "Author email:", - default: globalUserEmail.trim(), - }, - { - type: "confirm", - name: "shouldIncludeTheme", - message: "Include default theme?", - default: true, - }, - ], + questions: { + workspaceName: () => + input({ + message: "Workspace name:", + required: true, + validate: stringEmpty, + }), + workspaceDescription: () => + input({ + message: "Workspace description:", + default: "Untitled Workspace", + }), + systemName: () => + input({ + message: "System name:", + required: true, + validate: stringEmpty, + }), + systemDescription: () => + input({ + message: "System description:", + default: "Untitled System", + }), + authorName: () => + input({ + message: "Author Name:", + default: globalUserName.trim(), + }), + authorEmail: () => + input({ + message: "Author email:", + default: globalUserEmail.trim(), + }), + shouldIncludeTheme: () => + confirm({ + message: "Include default theme?", + default: true, + }), + } as QuestionsObject, actions: [ { type: "add", diff --git a/lib/utils/generator.ts b/lib/utils/generator.ts index 2be4510..892675c 100644 --- a/lib/utils/generator.ts +++ b/lib/utils/generator.ts @@ -1,5 +1,6 @@ +import type { CancelablePromise } from "@inquirer/type"; import chalk from "chalk"; -import type { Answers, PromptModule, QuestionCollection } from "inquirer"; +import type { PromptModule, QuestionCollection } from "inquirer"; import type { AddAction, AddManyAction, @@ -8,25 +9,36 @@ import type { } from "./actions"; import { ActionTypes, add, addMany, append } from "./actions"; -export type GeneratorDefinition = { +/** + * Added support for latest inquirer API + */ +export type QuestionsObject = { + [key: string]: () => CancelablePromise; +}; + +export type GeneratorDefinition< + A extends Record = Record, +> = { name: string; description: string; questions: | QuestionCollection - | ((prompt: PromptModule, generator: Generator) => Promise); + | ((prompt: PromptModule, generator: Generator) => Promise) + | QuestionsObject; actions: (AddAction | AddManyAction | AppendAction)[]; }; -export type Generator = GeneratorDefinition & { - destPath: string; - templates: Map; -}; +export type Generator> = + GeneratorDefinition & { + destPath: string; + templates: Map; + }; export type GetAnswers = Type extends GeneratorDefinition ? X : null; -async function executeAction( +async function executeAction>( action: ExtendedAction & (AddAction | AddManyAction | AppendAction), answers: A, ): Promise { @@ -46,16 +58,33 @@ async function executeAction( } } -export async function createGenerator( +export async function createGenerator>( prompt: PromptModule, generator: Generator, execute = executeAction, ): Promise { console.log(chalk.bold(chalk.gray(generator.description))); + // const sleep = (n: number) => new Promise((res) => setTimeout(res, n)); const answers = generator.questions instanceof Function ? await generator.questions(prompt, generator) - : await prompt(generator.questions); + : Array.isArray(generator.questions) + ? await prompt(generator.questions) + : await Object.entries( + generator.questions as QuestionsObject, + ).reduce( + async (answers, [name, prompt]) => { + const acc = await answers; + + const answer = await prompt?.(); + + return { + ...acc, + [name]: answer, + }; + }, + Promise.resolve({} as Record), + ); for await (const action of generator.actions) { await execute( diff --git a/lib/utils/questions/validators.ts b/lib/utils/questions/validators.ts index cf19edd..c2e19ea 100644 --- a/lib/utils/questions/validators.ts +++ b/lib/utils/questions/validators.ts @@ -6,7 +6,7 @@ import { getAllSystemElements } from "./system"; type Validator = Question["validate"]; -export const stringEmpty = (input: string) => input.length > 0; +export const stringEmpty = (input: string) => input?.length > 0; export const duplicatedSystemName = (input: string, answers: Answers) => { if (kebabCase(input) === kebabCase(answers?.systemName)) { diff --git a/package.json b/package.json index 965c17a..40a884b 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ }, "trustedDependencies": [".", "@biomejs/biome"], "dependencies": { + "@inquirer/prompts": "^5.1.2", "chalk": "^5.3.0", "change-case": "^5.4.4", "handlebars": "^4.7.8", @@ -27,6 +28,7 @@ "@biomejs/biome": "^1.8.3", "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", + "@inquirer/type": "^1.4.0", "@types/bun": "^1.1.6", "@types/inquirer": "^9.0.7", "@types/yargs": "^17.0.32", From b2af7c886802d6511c3a31ed01c2a9e2e00e8436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Zorro?= Date: Tue, 16 Jul 2024 11:11:28 -0500 Subject: [PATCH 2/9] refactor(generators): implemented inquirer@10 for constants generator --- lib/generators/constant.ts | 36 ++++++++++++++++++------------------ lib/templates/constant.hbs | 2 +- lib/utils/handlebars.ts | 4 ++++ 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/lib/generators/constant.ts b/lib/generators/constant.ts index 3a75d76..5789d81 100644 --- a/lib/generators/constant.ts +++ b/lib/generators/constant.ts @@ -1,26 +1,26 @@ -import type { Answers } from "inquirer"; +import { input } from "@inquirer/prompts"; import type { AppendAction } from "../utils/actions"; -import type { GeneratorDefinition } from "../utils/generator"; +import type { GeneratorDefinition, QuestionsObject } from "../utils/generator"; import { stringEmpty } from "../utils/questions/validators"; -const generator: GeneratorDefinition = { +const generator: GeneratorDefinition = { name: "Constant", description: "Create a new workspace constant", - questions: [ - { - type: "input", - name: "constantName", - message: "Constant:", - validate: stringEmpty, - }, - { - type: "input", - name: "constantValue", - message: "Value:", - default: "New Value", - validate: stringEmpty, - }, - ], + questions: { + constantName: () => + input({ + message: "Constant:", + required: true, + validate: stringEmpty, + }), + constantValue: () => + input({ + message: "Value:", + default: "New Value", + required: true, + validate: stringEmpty, + }), + } as QuestionsObject, actions: [ { type: "append", diff --git a/lib/templates/constant.hbs b/lib/templates/constant.hbs index 710aeaa..6f46593 100644 --- a/lib/templates/constant.hbs +++ b/lib/templates/constant.hbs @@ -1 +1 @@ - !const {{upperCase constantName}} "{{{constantValue}}}" \ No newline at end of file + !const {{upperCase (underscoreSpaces constantName)}} "{{{constantValue}}}" \ No newline at end of file diff --git a/lib/utils/handlebars.ts b/lib/utils/handlebars.ts index 2cc566f..47e2483 100644 --- a/lib/utils/handlebars.ts +++ b/lib/utils/handlebars.ts @@ -4,6 +4,7 @@ import { kebabCase, pascalCase } from "change-case"; import Handlebars from "handlebars"; export const removeSpaces = (txt = "") => txt.replace(/\s/g, ""); +export const underscoreSpaces = (txt = "") => txt.replace(/\s/g, "_"); Handlebars.registerHelper("kebabCase", (target) => kebabCase(target)); Handlebars.registerHelper("properCase", (target) => pascalCase(target)); @@ -12,6 +13,9 @@ Handlebars.registerHelper("upperCase", (target = "") => target.toUpperCase()); Handlebars.registerHelper("lowerCase", (target = "") => target.toLowerCase()); Handlebars.registerHelper("eq", (arg1, arg2) => arg1 === arg2); Handlebars.registerHelper("removeSpaces", (txt = "") => removeSpaces(txt)); +Handlebars.registerHelper("underscoreSpaces", (txt = "") => + underscoreSpaces(txt), +); export function compileSource>( sourceObject: Record, From d82b3755bab34c7a29ad01bb5d5c27d9c008c2fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Zorro?= Date: Tue, 16 Jul 2024 17:38:39 -0500 Subject: [PATCH 3/9] refactor(generators): implemented inquirer@10 for views and systems --- lib/generators/container.ts | 4 +- lib/generators/external-system.ts | 4 +- lib/generators/person.ts | 4 +- lib/generators/system.ts | 52 ++++---- lib/generators/view.ts | 93 +++++++------ lib/utils/generator.ts | 9 +- lib/utils/questions/relationships.test.ts | 6 +- lib/utils/questions/relationships.ts | 155 ++++++++++++++++++++-- lib/utils/questions/system.ts | 50 +++++++ 9 files changed, 282 insertions(+), 95 deletions(-) diff --git a/lib/generators/container.ts b/lib/generators/container.ts index 54f2c90..d5bee4c 100644 --- a/lib/generators/container.ts +++ b/lib/generators/container.ts @@ -1,8 +1,8 @@ import { resolve } from "node:path"; +import { Separator } from "@inquirer/prompts"; import { file } from "bun"; import { kebabCase, pascalCase } from "change-case"; import type { Answers, QuestionCollection } from "inquirer"; -import inquirer from "inquirer"; import type { AddAction, AppendAction } from "../utils/actions"; import type { GeneratorDefinition } from "../utils/generator"; import { removeSpaces } from "../utils/handlebars"; @@ -82,7 +82,7 @@ const generator: GeneratorDefinition = { prompt, { filterChoices: (elm) => - elm instanceof inquirer.Separator || + elm instanceof Separator || elm.value !== partialAnswers.systemName, ...relationshipDefaults, includeContainers: partialAnswers.systemName, diff --git a/lib/generators/external-system.ts b/lib/generators/external-system.ts index af3dc07..29788a8 100644 --- a/lib/generators/external-system.ts +++ b/lib/generators/external-system.ts @@ -1,5 +1,5 @@ +import { Separator } from "@inquirer/prompts"; import type { Answers, QuestionCollection } from "inquirer"; -import inquirer from "inquirer"; import type { AppendAction } from "../utils/actions"; import { whenFileExists } from "../utils/actions/utils"; import type { GeneratorDefinition } from "../utils/generator"; @@ -69,7 +69,7 @@ const generator: GeneratorDefinition = { prompt, { filterChoices: (elm) => - elm instanceof inquirer.Separator || + elm instanceof Separator || elm.value !== partialAnswers.systemName, ...relationshipDefaults, }, diff --git a/lib/generators/person.ts b/lib/generators/person.ts index 46f9096..7e8d815 100644 --- a/lib/generators/person.ts +++ b/lib/generators/person.ts @@ -1,5 +1,5 @@ +import { Separator } from "@inquirer/prompts"; import type { Answers, QuestionCollection } from "inquirer"; -import inquirer from "inquirer"; import type { AppendAction } from "../utils/actions"; import { whenFileExists } from "../utils/actions/utils"; import type { GeneratorDefinition } from "../utils/generator"; @@ -64,7 +64,7 @@ const generator: GeneratorDefinition = { prompt, { filterChoices: (elm) => - elm instanceof inquirer.Separator || + elm instanceof Separator || elm.value !== partialAnswers.systemName, ...relationshipDefaults, }, diff --git a/lib/generators/system.ts b/lib/generators/system.ts index f331d64..8c81885 100644 --- a/lib/generators/system.ts +++ b/lib/generators/system.ts @@ -1,7 +1,7 @@ -import type { Answers, QuestionCollection } from "inquirer"; +import { input } from "@inquirer/prompts"; import type { AddAction, AppendAction } from "../utils/actions"; import type { GeneratorDefinition } from "../utils/generator"; -import { getRelationships } from "../utils/questions/relationships"; +import { getRelationshipsForElement } from "../utils/questions/relationships"; import { chainValidators, stringEmpty, @@ -9,49 +9,45 @@ import { } from "../utils/questions/validators"; import { getWorkspaceJson, getWorkspacePath } from "../utils/workspace"; -const generator: GeneratorDefinition = { +const generator: GeneratorDefinition = { name: "System", description: "Create a new software system", - questions: async (prompt, generator) => { + questions: async (_, generator) => { const workspaceInfo = await getWorkspaceJson( getWorkspacePath(generator.destPath), ); - const questions: QuestionCollection = [ - { - type: "input", - name: "systemName", - message: "System Name:", - validate: chainValidators( - stringEmpty, - validateDuplicatedElements(workspaceInfo), - ), - }, - { - type: "input", - name: "systemDescription", - message: "System Description:", - default: "Untitled System", - validate: stringEmpty, - }, - ]; + + const systemName = await input({ + message: "System Name:", + validate: chainValidators( + stringEmpty, + validateDuplicatedElements(workspaceInfo), + ), + }); + + const systemDescription = await input({ + message: "System Description:", + default: "Untitled System", + validate: stringEmpty, + }); + const relationshipDefaults = { defaultRelationship: "Uses", defaultRelationshipType: "incoming", }; - const partialAnswers = await prompt(questions); - const relationships = await getRelationships( - partialAnswers.systemName, + const relationships = await getRelationshipsForElement( + systemName, workspaceInfo, - prompt, { ...relationshipDefaults, }, ); const compiledAnswers = { - ...partialAnswers, - elementName: partialAnswers.systemName, + systemName, + systemDescription, + elementName: systemName, relationships, }; diff --git a/lib/generators/view.ts b/lib/generators/view.ts index 557c391..3df3379 100644 --- a/lib/generators/view.ts +++ b/lib/generators/view.ts @@ -1,8 +1,8 @@ -import type { Answers, QuestionCollection } from "inquirer"; +import { input, select } from "@inquirer/prompts"; import type { AddAction } from "../utils/actions"; import { skipUnlessViewType, whenViewType } from "../utils/actions/utils"; import type { GeneratorDefinition } from "../utils/generator"; -import { getSystemQuestion } from "../utils/questions/system"; +import { getSystemQuestionAsPromise } from "../utils/questions/system"; import { chainValidators, stringEmpty, @@ -15,54 +15,59 @@ import { getWorkspaceJson, getWorkspacePath } from "../utils/workspace"; // - Filtered // // - System landscape // // - Deployment -const generator: GeneratorDefinition = { +const generator: GeneratorDefinition = { name: "View", description: "Create a new view", - questions: async (prompt, generator) => { + questions: async (_, generator) => { const workspaceInfo = await getWorkspaceJson( getWorkspacePath(generator.destPath), ); - const questions: QuestionCollection = [ - { - type: "list", - name: "viewType", - message: "View type:", - choices: [ - // "dynamic", - // "filtered", - "deployment", - "landscape", - ], - }, - await getSystemQuestion(workspaceInfo ?? generator.destPath, { - message: "System (to create view for):", - when: (answers) => answers.viewType !== "landscape", - }), - { - type: "input", - name: "viewName", - message: "View name:", - validate: chainValidators( - stringEmpty, - validateDuplicatedViews(workspaceInfo), - ), - }, - { - type: "input", - name: "viewDescription", - message: "View description:", - default: "Untitled view", - }, - { - type: "input", - name: "instanceDescription", - message: "System Instance description:", - default: "System instance", - when: (answers) => answers.viewType === "deployment", - }, - ]; - return prompt(questions); + const viewType = await select({ + message: "View type:", + choices: [ + // "dynamic", + // "filtered", + { name: "Deployment", value: "deployment" }, + { name: "Landscape", value: "landscape" }, + ], + }); + + const systemName = + viewType !== "landscape" + ? await getSystemQuestionAsPromise( + workspaceInfo ?? generator.destPath, + ) + : undefined; + + const viewName = await input({ + message: "View name:", + validate: chainValidators( + stringEmpty, + validateDuplicatedViews(workspaceInfo), + ), + }); + + const viewDescription = await input({ + message: "View description:", + default: "Untitled view", + }); + + const instanceDescription = + viewType === "deployment" + ? await input({ + message: "System Instance description:", + default: "System instance", + }) + : undefined; + + return { + viewType, + viewName, + viewDescription, + systemName, + instanceDescription, + }; }, actions: [ { diff --git a/lib/utils/generator.ts b/lib/utils/generator.ts index 892675c..807dddb 100644 --- a/lib/utils/generator.ts +++ b/lib/utils/generator.ts @@ -12,8 +12,8 @@ import { ActionTypes, add, addMany, append } from "./actions"; /** * Added support for latest inquirer API */ -export type QuestionsObject = { - [key: string]: () => CancelablePromise; +export type QuestionsObject = { + [key: string]: () => CancelablePromise; }; export type GeneratorDefinition< @@ -64,10 +64,11 @@ export async function createGenerator>( execute = executeAction, ): Promise { console.log(chalk.bold(chalk.gray(generator.description))); - // const sleep = (n: number) => new Promise((res) => setTimeout(res, n)); + const answers = generator.questions instanceof Function - ? await generator.questions(prompt, generator) + ? // TODO: Remove "prompt" argument from generator.questions + await generator.questions(prompt, generator) : Array.isArray(generator.questions) ? await prompt(generator.questions) : await Object.entries( diff --git a/lib/utils/questions/relationships.test.ts b/lib/utils/questions/relationships.test.ts index 8804b12..4f0719a 100644 --- a/lib/utils/questions/relationships.test.ts +++ b/lib/utils/questions/relationships.test.ts @@ -1,6 +1,6 @@ import { describe, expect, mock, test } from "bun:test"; +import { Separator } from "@inquirer/prompts"; import type { PromptModule } from "inquirer"; -import inquirer from "inquirer"; import type { StructurizrWorkspace } from "../workspace"; import { getRelationships } from "./relationships"; @@ -81,12 +81,12 @@ describe("relationships", () => { name: "relationships", message: expect.any(String), choices: [ - expect.any(inquirer.Separator), + expect.any(Separator), { name: "🟦 SomeSystem", value: "SomeSystem", }, - expect.any(inquirer.Separator), + expect.any(Separator), { name: "👤 SomePerson", value: "SomePerson", diff --git a/lib/utils/questions/relationships.ts b/lib/utils/questions/relationships.ts index 1151a0e..37610b2 100644 --- a/lib/utils/questions/relationships.ts +++ b/lib/utils/questions/relationships.ts @@ -1,6 +1,8 @@ +import { Separator, checkbox, input, select } from "@inquirer/prompts"; +import type { CancelablePromise } from "@inquirer/type"; import { pascalCase } from "change-case"; import type { Answers, PromptModule, Question } from "inquirer"; -import inquirer from "inquirer"; +import type { QuestionsObject } from "../generator"; import { removeSpaces } from "../handlebars"; import { labelElementByTags } from "../labels"; import type { StructurizrWorkspace } from "../workspace"; @@ -25,7 +27,7 @@ type GetRelationshipsOptions = { when?: Question["when"]; validate?: Question["validate"]; filterChoices?: ( - elm: inquirer.Separator | { name: string; value: string }, + elm: Separator | { name: string; value: string }, pos: number, arr: unknown[], ) => boolean; @@ -47,6 +49,7 @@ export const defaultParser = (rawRelationshipMap: Record) => { ); }; +// TODO: Remove in favor of relationshipsForElementAsPromises export const relationshipsForElement = ( relationshipName: string, elementName: string, @@ -90,6 +93,46 @@ export const relationshipsForElement = ( ]; }; +export const relationshipsForElementAsPromises = ( + relationshipName: string, + elementName: string, + { + defaultRelationship = "Interacts with", + defaultRelationshipType = "incoming", + defaultTechnology = "Web/HTTP", + }: RelationshipForElementOptions = {}, +): Record CancelablePromise> => { + const elementNamePascalCase = pascalCase(removeSpaces(relationshipName)); + + return { + [`${elementNamePascalCase}_relationshipType`]: () => + select({ + message: `Relationship type for ${relationshipName}`, + choices: [ + { + name: `outgoing (${elementName} → ${relationshipName})`, + value: "outgoing", + }, + { + name: `incoming (${relationshipName} → ${elementName})`, + value: "incoming", + }, + ], + default: defaultRelationshipType, + }), + [`${elementNamePascalCase}_relationship`]: () => + input({ + message: `Relationship with ${relationshipName}:`, + default: defaultRelationship, + }), + [`${elementNamePascalCase}_technology`]: () => + input({ + message: "Technology:", + default: defaultTechnology, + }), + }; +}; + const findSystemContainers = ( systemName: string, systems: SoftwareSystem[], @@ -103,14 +146,15 @@ const findSystemContainers = ( const separator = ( name: string, elements: unknown[], -): inquirer.Separator | unknown[] => { +): Separator | unknown[] => { const maybeSeparator = elements.length - ? new inquirer.Separator(`-- ${name} --`) + ? new Separator(`-- ${name} --`) : []; return maybeSeparator; }; +// TODO: Remove in favor of getRelationshipsForElement export async function getRelationships( elementName: string, workspaceInfo: StructurizrWorkspace | undefined, @@ -146,11 +190,11 @@ export async function getRelationships( ...softwareSystems, separator("People", people), ...people, - ] as (SoftwareElement | inquirer.Separator)[] + ] as (SoftwareElement | Separator)[] ) .flat() .map((elm) => - elm instanceof inquirer.Separator + elm instanceof Separator ? elm : { name: `${labelElementByTags(elm.tags)} ${elm.name}`, @@ -159,10 +203,7 @@ export async function getRelationships( ) .filter(filterChoices); - if ( - !systemElements.filter((elm) => !(elm instanceof inquirer.Separator)) - .length - ) + if (!systemElements.filter((elm) => !(elm instanceof Separator)).length) return {}; const { relationships } = await prompt({ @@ -188,3 +229,97 @@ export async function getRelationships( return parse(relationshipMap); } + +export async function getRelationshipsForElement( + elementName: string, + workspaceInfo: StructurizrWorkspace | undefined, + { + validate = () => true, + filterChoices = () => true, + parse = defaultParser, + message = "Relates to elements:", + defaultRelationship = "Interacts with", + defaultRelationshipType = "outgoing", + defaultTechnology = "Web/HTTP", + includeContainers, + }: GetRelationshipsOptions = {}, +): Promise> { + if (!workspaceInfo) return {}; + + const softwareSystems = workspaceInfo.model?.softwareSystems ?? []; + const people = workspaceInfo.model?.people ?? []; + const containers = includeContainers + ? findSystemContainers( + includeContainers, + workspaceInfo.model?.softwareSystems, + ) + : []; + + const systemElements = ( + [ + separator(`Containers (${includeContainers})`, containers), + ...containers, + separator("Systems", softwareSystems), + ...softwareSystems, + separator("People", people), + ...people, + ] as (SoftwareElement | Separator)[] + ) + .flat() + .map((elm) => + elm instanceof Separator + ? elm + : { + name: `${labelElementByTags(elm.tags)} ${elm.name}`, + value: elm.name, + }, + ) + .filter(filterChoices); + + if (!systemElements.filter((elm) => !(elm instanceof Separator)).length) + return {}; + + const relationshipNames = await checkbox({ + message, + choices: systemElements, + validate, + }); + + if (!relationshipNames.length) return {}; + + const relationshipsMap: Record = {}; + + for await (const relationshipName of relationshipNames) { + const relationshipQuestions = relationshipsForElementAsPromises( + relationshipName, + elementName, + { defaultRelationship, defaultRelationshipType, defaultTechnology }, + ); + + const relationship = await Object.entries( + relationshipQuestions as QuestionsObject, + ).reduce( + async (answers, [name, prompt]) => { + const acc = await answers; + const answer = await prompt?.(); + + if (!answer) return acc; + + return { + ...acc, + [name]: answer, + }; + }, + Promise.resolve({} as { [key: string]: string }), + ); + + const elementNamePascalCase = pascalCase( + removeSpaces(relationshipName), + ); + + relationshipsMap[elementNamePascalCase] = + parse(relationship)[relationshipName]; + } + + return relationshipsMap; +} diff --git a/lib/utils/questions/system.ts b/lib/utils/questions/system.ts index 69fb040..270ffa4 100644 --- a/lib/utils/questions/system.ts +++ b/lib/utils/questions/system.ts @@ -1,5 +1,7 @@ import { existsSync } from "node:fs"; import { resolve } from "node:path"; +import { input, select } from "@inquirer/prompts"; +import { CancelablePromise } from "@inquirer/type"; import { kebabCase } from "change-case"; import type { Answers, AsyncDynamicQuestionProperty, Question } from "inquirer"; import { getWorkspacePath } from "../workspace"; @@ -42,6 +44,7 @@ export function getAllSystemElements( return systemElements; } +// TODO: Remove in favor of getSystemQuestionAsPromise export async function getSystemQuestion( workspace: string | StructurizrWorkspace, { @@ -98,3 +101,50 @@ export async function getSystemQuestion( return systemQuestion; } + +export function getSystemQuestionAsPromise( + workspace: string | StructurizrWorkspace, + options: { message: string } = { + message: "Relates to system:", + }, +): CancelablePromise { + const voidPromise: CancelablePromise = new CancelablePromise( + (resolve) => resolve(""), + ); + const workspaceInfo = typeof workspace !== "string" && workspace; + + if (workspaceInfo) { + const systems = (workspaceInfo.model?.softwareSystems ?? []) + .filter((system) => !system.tags.split(",").includes("External")) + .map((system) => ({ name: system.name, value: system.name })); + + const systemQuestion = select({ + message: options.message, + choices: systems, + }); + + return systemQuestion; + } + + const workspacePath = typeof workspace === "string" && workspace; + if (!workspacePath) return voidPromise; + + const workspaceFolder = getWorkspacePath(workspacePath); + + return input({ + message: options.message, + validate: async (input) => { + if (workspaceFolder) { + const systemPath = resolve( + workspaceFolder, + `containers/${kebabCase(input)}`, + ); + if (existsSync(systemPath)) return true; + } + + throw new Error( + `System "${input}" does not exist in the workspace.`, + ); + }, + }); +} From 128b44eb407106a09bca9613eb520160608510b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Zorro?= Date: Wed, 17 Jul 2024 10:13:50 -0500 Subject: [PATCH 4/9] refactor(generators): implemented inquirer@10 for person --- lib/generators/person.ts | 79 +++++++++++++--------------- lib/generators/system.ts | 8 ++- lib/utils/questions/relationships.ts | 56 +++++++++++--------- 3 files changed, 70 insertions(+), 73 deletions(-) diff --git a/lib/generators/person.ts b/lib/generators/person.ts index 7e8d815..a1f0e7a 100644 --- a/lib/generators/person.ts +++ b/lib/generators/person.ts @@ -1,14 +1,13 @@ -import { Separator } from "@inquirer/prompts"; -import type { Answers, QuestionCollection } from "inquirer"; +import { Separator, input } from "@inquirer/prompts"; import type { AppendAction } from "../utils/actions"; import { whenFileExists } from "../utils/actions/utils"; import type { GeneratorDefinition } from "../utils/generator"; import { + addRelationshipsToElement, defaultParser, - getRelationships, - relationshipsForElement, + resolveRelationshipForElement, } from "../utils/questions/relationships"; -import { getSystemQuestion } from "../utils/questions/system"; +import { getSystemQuestionAsPromise } from "../utils/questions/system"; import { chainValidators, stringEmpty, @@ -16,62 +15,56 @@ import { } from "../utils/questions/validators"; import { getWorkspaceJson, getWorkspacePath } from "../utils/workspace"; -const generator: GeneratorDefinition = { +const generator: GeneratorDefinition = { name: "Person", description: "Create a new person (customer, user, etc)", - questions: async (prompt, generator) => { + questions: async (_, generator) => { const workspaceInfo = await getWorkspaceJson( getWorkspacePath(generator.destPath), ); - const questions: QuestionCollection = [ - await getSystemQuestion(workspaceInfo ?? generator.destPath), - { - type: "input", - name: "elementName", - message: "Person name:", - validate: chainValidators( - stringEmpty, - validateDuplicatedElements(workspaceInfo), - ), - }, - { - type: "input", - name: "personDescription", - message: "Person description:", - default: "Default user", - }, - ]; - - const partialAnswers = await prompt(questions); - const relationshipDefaults = { - defaultRelationship: "Consumes", - defaultRelationshipType: "outgoing", - }; + const systemName = await getSystemQuestionAsPromise( + workspaceInfo ?? generator.destPath, + ); - const relationshipWithSystem = await prompt( - relationshipsForElement( - partialAnswers.systemName, - partialAnswers.elementName, - relationshipDefaults, + const elementName = await input({ + message: "Person name:", + validate: chainValidators( + stringEmpty, + validateDuplicatedElements(workspaceInfo), ), + }); + + const personDescription = await input({ + message: "Person description:", + default: "Default user", + }); + + const relationshipWithSystem = await resolveRelationshipForElement( + systemName, + elementName, + { + defaultRelationship: "Consumes", + defaultRelationshipType: "outgoing", + }, ); const mainRelationship = defaultParser(relationshipWithSystem); - const relationships = await getRelationships( - partialAnswers.elementName, + const relationships = await addRelationshipsToElement( + elementName, workspaceInfo, - prompt, { filterChoices: (elm) => - elm instanceof Separator || - elm.value !== partialAnswers.systemName, - ...relationshipDefaults, + elm instanceof Separator || elm.value !== systemName, + defaultRelationship: "Interacts with", + defaultRelationshipType: "outgoing", }, ); const compiledAnswers = { - ...partialAnswers, + systemName, + personDescription, + elementName, includeSource: "relationships/_people.dsl", includeTabs: " ", relationships: { ...mainRelationship, ...relationships }, diff --git a/lib/generators/system.ts b/lib/generators/system.ts index 8c81885..cad3467 100644 --- a/lib/generators/system.ts +++ b/lib/generators/system.ts @@ -1,7 +1,7 @@ import { input } from "@inquirer/prompts"; import type { AddAction, AppendAction } from "../utils/actions"; import type { GeneratorDefinition } from "../utils/generator"; -import { getRelationshipsForElement } from "../utils/questions/relationships"; +import { addRelationshipsToElement } from "../utils/questions/relationships"; import { chainValidators, stringEmpty, @@ -36,12 +36,10 @@ const generator: GeneratorDefinition = { defaultRelationshipType: "incoming", }; - const relationships = await getRelationshipsForElement( + const relationships = await addRelationshipsToElement( systemName, workspaceInfo, - { - ...relationshipDefaults, - }, + relationshipDefaults, ); const compiledAnswers = { diff --git a/lib/utils/questions/relationships.ts b/lib/utils/questions/relationships.ts index 37610b2..0f5dd0d 100644 --- a/lib/utils/questions/relationships.ts +++ b/lib/utils/questions/relationships.ts @@ -49,7 +49,7 @@ export const defaultParser = (rawRelationshipMap: Record) => { ); }; -// TODO: Remove in favor of relationshipsForElementAsPromises +// TODO: Remove in favor of resolveRelationshipForElement export const relationshipsForElement = ( relationshipName: string, elementName: string, @@ -93,7 +93,28 @@ export const relationshipsForElement = ( ]; }; -export const relationshipsForElementAsPromises = ( +const resolveRelationshipPromises = async ( + relationshipPromises: QuestionsObject, +): Promise> => { + const relationshipMap = await Object.entries(relationshipPromises).reduce( + async (answers, [name, prompt]) => { + const acc = await answers; + const answer = await prompt?.(); + + if (!answer) return acc; + + return { + ...acc, + [name]: answer, + }; + }, + Promise.resolve({} as { [key: string]: string }), + ); + + return relationshipMap; +}; + +export const resolveRelationshipForElement = async ( relationshipName: string, elementName: string, { @@ -101,10 +122,10 @@ export const relationshipsForElementAsPromises = ( defaultRelationshipType = "incoming", defaultTechnology = "Web/HTTP", }: RelationshipForElementOptions = {}, -): Record CancelablePromise> => { +): Promise> => { const elementNamePascalCase = pascalCase(removeSpaces(relationshipName)); - return { + const relationshipPromises = { [`${elementNamePascalCase}_relationshipType`]: () => select({ message: `Relationship type for ${relationshipName}`, @@ -131,6 +152,8 @@ export const relationshipsForElementAsPromises = ( default: defaultTechnology, }), }; + + return resolveRelationshipPromises(relationshipPromises); }; const findSystemContainers = ( @@ -154,7 +177,7 @@ const separator = ( return maybeSeparator; }; -// TODO: Remove in favor of getRelationshipsForElement +// TODO: Remove in favor of addRelationshipsToElement export async function getRelationships( elementName: string, workspaceInfo: StructurizrWorkspace | undefined, @@ -230,7 +253,7 @@ export async function getRelationships( return parse(relationshipMap); } -export async function getRelationshipsForElement( +export async function addRelationshipsToElement( elementName: string, workspaceInfo: StructurizrWorkspace | undefined, { @@ -290,35 +313,18 @@ export async function getRelationshipsForElement( const relationshipsMap: Record = {}; for await (const relationshipName of relationshipNames) { - const relationshipQuestions = relationshipsForElementAsPromises( + const relationship = await resolveRelationshipForElement( relationshipName, elementName, { defaultRelationship, defaultRelationshipType, defaultTechnology }, ); - const relationship = await Object.entries( - relationshipQuestions as QuestionsObject, - ).reduce( - async (answers, [name, prompt]) => { - const acc = await answers; - const answer = await prompt?.(); - - if (!answer) return acc; - - return { - ...acc, - [name]: answer, - }; - }, - Promise.resolve({} as { [key: string]: string }), - ); - const elementNamePascalCase = pascalCase( removeSpaces(relationshipName), ); relationshipsMap[elementNamePascalCase] = - parse(relationship)[relationshipName]; + parse(relationship)[elementNamePascalCase]; } return relationshipsMap; From 6c09869cb2e29d8fbc5b32aa8c4b84a85de5ec92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Zorro?= Date: Wed, 17 Jul 2024 10:14:16 -0500 Subject: [PATCH 5/9] feat(general): added option --export to leverage structurizr-cli export --- lib/main.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/main.ts b/lib/main.ts index 9e3d290..d8383b6 100644 --- a/lib/main.ts +++ b/lib/main.ts @@ -1,4 +1,5 @@ import { relative, resolve } from "node:path"; +import { $ } from "bun"; import chalk from "chalk"; import { capitalCase } from "change-case"; import inquirer from "inquirer"; @@ -27,6 +28,12 @@ const args = await yargs(hideBin(process.argv)) .option("dest", { default: ".", desc: "Target architecture folder", + }) + .option("export", { + alias: "e", + type: "boolean", + default: false, + desc: "Use structurizr-cli to export the workspace to JSON", }).argv; console.log( @@ -36,10 +43,19 @@ Create a Structurizr DSL scaffolding in seconds! `), ); +// TODO: Remove const prompt = inquirer.createPromptModule(); const destPath = resolve(process.cwd(), args.dest); const workspacePath = getWorkspacePath(destPath); +const exportWorkspace = async (path: string) => { + if (!args.export) return; + const workspacePath = getWorkspacePath(path); + if (!workspacePath) return; + + return $`structurizr-cli export -w ${workspacePath}/workspace.dsl -f json -o ${workspacePath} || true`; +}; + const { workspaceGenerator, ...otherGenerators } = generators; if (!workspacePath) { @@ -59,6 +75,9 @@ Let's create a new one by answering the questions below. }; await createGenerator(prompt, generator); + await exportWorkspace( + relative(process.cwd(), destPath) || process.cwd(), + ); process.exit(0); } catch (err) { console.error(err); @@ -96,6 +115,7 @@ try { }; await createGenerator(prompt, generator); + await exportWorkspace(relative(process.cwd(), workspacePath)); process.exit(0); } catch (err) { console.error(err); From c7668e809397bfa15a3207bb349f6a73f0dcc662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Zorro?= Date: Wed, 17 Jul 2024 11:06:43 -0500 Subject: [PATCH 6/9] refactor(generators): implemented inquirer@10 for external-systems --- lib/generators/container.ts | 1 + lib/generators/external-system.ts | 72 ++++++++++++-------------- lib/generators/person.ts | 7 +-- lib/generators/system.ts | 2 +- lib/generators/view.ts | 6 +-- lib/utils/questions/system.ts | 4 +- lib/utils/questions/validators.test.ts | 14 ++++- lib/utils/questions/validators.ts | 19 ++++--- package.json | 2 +- 9 files changed, 68 insertions(+), 59 deletions(-) diff --git a/lib/generators/container.ts b/lib/generators/container.ts index d5bee4c..6ceb352 100644 --- a/lib/generators/container.ts +++ b/lib/generators/container.ts @@ -36,6 +36,7 @@ const generator: GeneratorDefinition = { type: "input", name: "elementName", message: "Container Name:", + // FIXME: broken validate: chainValidators( stringEmpty, duplicatedSystemName, diff --git a/lib/generators/external-system.ts b/lib/generators/external-system.ts index 29788a8..f64c956 100644 --- a/lib/generators/external-system.ts +++ b/lib/generators/external-system.ts @@ -1,14 +1,13 @@ -import { Separator } from "@inquirer/prompts"; -import type { Answers, QuestionCollection } from "inquirer"; +import { Separator, input } from "@inquirer/prompts"; import type { AppendAction } from "../utils/actions"; import { whenFileExists } from "../utils/actions/utils"; import type { GeneratorDefinition } from "../utils/generator"; import { + addRelationshipsToElement, defaultParser, - getRelationships, - relationshipsForElement, + resolveRelationshipForElement, } from "../utils/questions/relationships"; -import { getSystemQuestion } from "../utils/questions/system"; +import { resolveSystemQuestion } from "../utils/questions/system"; import { chainValidators, duplicatedSystemName, @@ -17,66 +16,59 @@ import { } from "../utils/questions/validators"; import { getWorkspaceJson, getWorkspacePath } from "../utils/workspace"; -const generator: GeneratorDefinition = { +const generator: GeneratorDefinition = { name: "External System", description: "Create a new external system", - questions: async (prompt, generator) => { + questions: async (_, generator) => { const workspaceInfo = await getWorkspaceJson( getWorkspacePath(generator.destPath), ); - const systemQuestion = await getSystemQuestion( + + const systemName = await resolveSystemQuestion( workspaceInfo ?? generator.destPath, ); - const questions: QuestionCollection = [ - systemQuestion, - { - type: "input", - name: "elementName", - message: "External system name:", - validate: chainValidators( - stringEmpty, - duplicatedSystemName, - validateDuplicatedElements(workspaceInfo), - ), - }, - { - type: "input", - name: "extSystemDescription", - message: "System description:", - default: "Untitled System", - }, - ]; + const elementName = await input({ + message: "External system name:", + required: true, + validate: chainValidators( + stringEmpty, + duplicatedSystemName, + validateDuplicatedElements(workspaceInfo), + )({ systemName }), + }); + + const extSystemDescription = await input({ + message: "System description:", + default: "Untitled System", + }); - const partialAnswers = await prompt(questions); const relationshipDefaults = { defaultRelationship: "Interacts with", defaultRelationshipType: "incoming", }; - const relationshipWithSystem = await prompt( - relationshipsForElement( - partialAnswers.systemName, - partialAnswers.elementName, - relationshipDefaults, - ), + const relationshipWithSystem = await resolveRelationshipForElement( + systemName, + elementName, + relationshipDefaults, ); const mainRelationship = defaultParser(relationshipWithSystem); - const relationships = await getRelationships( - partialAnswers.elementName, + const relationships = await addRelationshipsToElement( + elementName, workspaceInfo, - prompt, { filterChoices: (elm) => - elm instanceof Separator || - elm.value !== partialAnswers.systemName, + elm instanceof Separator || elm.value !== systemName, ...relationshipDefaults, }, ); const compiledAnswers = { - ...partialAnswers, + systemName, + elementName, + extSystemDescription, includeSource: "relationships/_external.dsl", includeTabs: " ", relationships: { ...mainRelationship, ...relationships }, diff --git a/lib/generators/person.ts b/lib/generators/person.ts index a1f0e7a..086a753 100644 --- a/lib/generators/person.ts +++ b/lib/generators/person.ts @@ -7,7 +7,7 @@ import { defaultParser, resolveRelationshipForElement, } from "../utils/questions/relationships"; -import { getSystemQuestionAsPromise } from "../utils/questions/system"; +import { resolveSystemQuestion } from "../utils/questions/system"; import { chainValidators, stringEmpty, @@ -23,16 +23,17 @@ const generator: GeneratorDefinition = { getWorkspacePath(generator.destPath), ); - const systemName = await getSystemQuestionAsPromise( + const systemName = await resolveSystemQuestion( workspaceInfo ?? generator.destPath, ); const elementName = await input({ message: "Person name:", + required: true, validate: chainValidators( stringEmpty, validateDuplicatedElements(workspaceInfo), - ), + )(), }); const personDescription = await input({ diff --git a/lib/generators/system.ts b/lib/generators/system.ts index cad3467..e147fcc 100644 --- a/lib/generators/system.ts +++ b/lib/generators/system.ts @@ -22,7 +22,7 @@ const generator: GeneratorDefinition = { validate: chainValidators( stringEmpty, validateDuplicatedElements(workspaceInfo), - ), + )(), }); const systemDescription = await input({ diff --git a/lib/generators/view.ts b/lib/generators/view.ts index 3df3379..bfa1410 100644 --- a/lib/generators/view.ts +++ b/lib/generators/view.ts @@ -2,7 +2,7 @@ import { input, select } from "@inquirer/prompts"; import type { AddAction } from "../utils/actions"; import { skipUnlessViewType, whenViewType } from "../utils/actions/utils"; import type { GeneratorDefinition } from "../utils/generator"; -import { getSystemQuestionAsPromise } from "../utils/questions/system"; +import { resolveSystemQuestion } from "../utils/questions/system"; import { chainValidators, stringEmpty, @@ -35,7 +35,7 @@ const generator: GeneratorDefinition = { const systemName = viewType !== "landscape" - ? await getSystemQuestionAsPromise( + ? await resolveSystemQuestion( workspaceInfo ?? generator.destPath, ) : undefined; @@ -45,7 +45,7 @@ const generator: GeneratorDefinition = { validate: chainValidators( stringEmpty, validateDuplicatedViews(workspaceInfo), - ), + )(), }); const viewDescription = await input({ diff --git a/lib/utils/questions/system.ts b/lib/utils/questions/system.ts index 270ffa4..208a344 100644 --- a/lib/utils/questions/system.ts +++ b/lib/utils/questions/system.ts @@ -44,7 +44,7 @@ export function getAllSystemElements( return systemElements; } -// TODO: Remove in favor of getSystemQuestionAsPromise +// TODO: Remove in favor of resolveSystemQuestion export async function getSystemQuestion( workspace: string | StructurizrWorkspace, { @@ -102,7 +102,7 @@ export async function getSystemQuestion( return systemQuestion; } -export function getSystemQuestionAsPromise( +export function resolveSystemQuestion( workspace: string | StructurizrWorkspace, options: { message: string } = { message: "Relates to system:", diff --git a/lib/utils/questions/validators.test.ts b/lib/utils/questions/validators.test.ts index da92448..63261f5 100644 --- a/lib/utils/questions/validators.test.ts +++ b/lib/utils/questions/validators.test.ts @@ -14,32 +14,44 @@ describe("validators", () => { (input) => input.size > 0, async (input) => input.weight > 0 || "Error message", (input) => input.height > 0, - ); + (input, answers) => answers?.test === input.test, + )({ test: "test" }); const response1 = await validate?.({ size: 1, weight: 1, height: 1, + test: "test", }); expect(response1).toBeTrue(); const response2 = await validate?.({ size: 1, weight: 1, height: 0, + test: "test", }); expect(response2).toBeFalse(); const response3 = await validate?.({ size: 1, weight: 0, height: 1, + test: "test", }); expect(response3).toEqual("Error message"); const response4 = await validate?.({ size: 0, weight: 1, height: 1, + test: "test", }); expect(response4).toBeFalse(); + const response5 = await validate?.({ + size: 1, + weight: 1, + height: 1, + test: "another value", + }); + expect(response5).toBeFalse(); }); }); describe("duplicatedSystemName", () => { diff --git a/lib/utils/questions/validators.ts b/lib/utils/questions/validators.ts index c2e19ea..eb081ec 100644 --- a/lib/utils/questions/validators.ts +++ b/lib/utils/questions/validators.ts @@ -61,13 +61,16 @@ export const validateDuplicatedViews = return true; }; -export function chainValidators(...validators: Validator[]): Validator { - return async (input: unknown, answers?: Answers | undefined) => { - for await (const validator of validators) { - const validation = await validator?.(input, answers); - if (validation !== true) return validation ?? false; - } +export function chainValidators( + ...validators: Validator[] +): (answers?: Record) => Validator { + return (answers = {}) => + async (input: unknown) => { + for await (const validator of validators) { + const validation = await validator?.(input, answers); + if (validation !== true) return validation ?? false; + } - return true; - }; + return true; + }; } diff --git a/package.json b/package.json index 40a884b..62ac5ac 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "scripts": { "build:dev": "bun build ./lib/main.ts --compile --watch --outfile ./dist/scfz", - "test:dev": "bun test --watch", + "test:dev": "bun test --watch --coverage", "test": "bun test", "test:ci": "bun test --coverage", "postinstall": "scripts/install-git-hooks.sh", From d502f52a9eb49723c846b1d6f029cde4a88fd79a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Zorro?= Date: Wed, 17 Jul 2024 11:16:06 -0500 Subject: [PATCH 7/9] refactor(generators): implemented inquirer@10 for containers --- lib/generators/container.ts | 110 +++++++++++++++++------------------- 1 file changed, 51 insertions(+), 59 deletions(-) diff --git a/lib/generators/container.ts b/lib/generators/container.ts index 6ceb352..24dd5ff 100644 --- a/lib/generators/container.ts +++ b/lib/generators/container.ts @@ -1,13 +1,12 @@ import { resolve } from "node:path"; -import { Separator } from "@inquirer/prompts"; +import { Separator, input, select } from "@inquirer/prompts"; import { file } from "bun"; import { kebabCase, pascalCase } from "change-case"; -import type { Answers, QuestionCollection } from "inquirer"; import type { AddAction, AppendAction } from "../utils/actions"; import type { GeneratorDefinition } from "../utils/generator"; import { removeSpaces } from "../utils/handlebars"; -import { getRelationships } from "../utils/questions/relationships"; -import { getSystemQuestion } from "../utils/questions/system"; +import { addRelationshipsToElement } from "../utils/questions/relationships"; +import { resolveSystemQuestion } from "../utils/questions/system"; import { chainValidators, duplicatedSystemName, @@ -16,84 +15,77 @@ import { } from "../utils/questions/validators"; import { getWorkspaceJson, getWorkspacePath } from "../utils/workspace"; -const generator: GeneratorDefinition = { +const generator: GeneratorDefinition = { name: "Container", description: "Create a new system container", - questions: async (prompt, generator) => { + questions: async (_, generator) => { const workspaceInfo = await getWorkspaceJson( getWorkspacePath(generator.destPath), ); - const systemQuestion = await getSystemQuestion( + + const systemName = await resolveSystemQuestion( workspaceInfo ?? generator.destPath, - { - message: "Parent system:", - }, + { message: "Parent system:" }, ); - const questions: QuestionCollection = [ - systemQuestion, - { - type: "input", - name: "elementName", - message: "Container Name:", - // FIXME: broken - validate: chainValidators( - stringEmpty, - duplicatedSystemName, - validateDuplicatedElements(workspaceInfo), - ), - }, - { - type: "input", - name: "containerDescription", - message: "Container Description:", - default: "Untitled Container", - validate: stringEmpty, - }, - { - type: "list", - name: "containerType", - message: "Container type:", - choices: [ - "EventBus", - "MessageBroker", - "Function", - "Database", - "WebApp", - "MobileApp", - "None of the above", - ], - }, - { - type: "input", - name: "containerTechnology", - message: "Container technology:", - }, - ]; + const elementName = await input({ + message: "Container Name:", + required: true, + validate: chainValidators( + stringEmpty, + duplicatedSystemName, + validateDuplicatedElements(workspaceInfo), + )({ systemName }), + }); + + const containerDescription = await input({ + message: "Container Description:", + default: "Untitled Container", + validate: stringEmpty, + }); + + const containerType = await select({ + message: "Container type:", + default: "None of the above", + choices: [ + { name: "EventBus", value: "EventBus" }, + { name: "MessageBroker", value: "MessageBroker" }, + { name: "Function", value: "Function" }, + { name: "Database", value: "Database" }, + { name: "WebApp", value: "WebApp" }, + { name: "MobileApp", value: "MobileApp" }, + { name: "None of the above", value: "None of the above" }, + ], + }); + + const containerTechnology = await input({ + message: "Container technology:", + }); const relationshipDefaults = { defaultRelationship: "Uses", defaultRelationshipType: "incoming", }; - const partialAnswers = await prompt(questions); - const relationships = await getRelationships( - partialAnswers.elementName, + const relationships = await addRelationshipsToElement( + elementName, workspaceInfo, - prompt, { filterChoices: (elm) => - elm instanceof Separator || - elm.value !== partialAnswers.systemName, + elm instanceof Separator || elm.value !== systemName, ...relationshipDefaults, - includeContainers: partialAnswers.systemName, + includeContainers: systemName, }, ); const compiledAnswers = { - ...partialAnswers, + systemName, + elementName, + containerDescription, + containerType, + containerTechnology, includeTabs: "", - includeSource: `${kebabCase(partialAnswers.systemName)}.dsl`, + includeSource: `${kebabCase(systemName)}.dsl`, relationships, }; From 35dace1c70cdcea20b9d12a5454db9d414316652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Zorro?= Date: Wed, 17 Jul 2024 11:59:38 -0500 Subject: [PATCH 8/9] refactor(generators): implemented inquirer@10 for relationships --- lib/generators/relationship.ts | 23 ++++++++--------------- lib/utils/labels.ts | 3 +++ lib/utils/questions/system.ts | 15 +++++++++++++-- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/lib/generators/relationship.ts b/lib/generators/relationship.ts index e4ce5af..d2a1a35 100644 --- a/lib/generators/relationship.ts +++ b/lib/generators/relationship.ts @@ -1,21 +1,22 @@ -import type { Answers } from "inquirer"; +import { select } from "@inquirer/prompts"; import type { AppendAction } from "../utils/actions"; import type { GeneratorDefinition } from "../utils/generator"; import { elementTypeByTags, labelElementByTags } from "../utils/labels"; -import { getRelationships } from "../utils/questions/relationships"; +import { addRelationshipsToElement } from "../utils/questions/relationships"; import { getAllSystemElements } from "../utils/questions/system"; import { getWorkspaceJson, getWorkspacePath } from "../utils/workspace"; -const generator: GeneratorDefinition = { +const generator: GeneratorDefinition = { name: "Relationship", description: "Create a new relationship between elements", - questions: async (prompt, generator) => { + questions: async (_, generator) => { const workspaceInfo = await getWorkspaceJson( getWorkspacePath(generator.destPath), ); const systemElements = getAllSystemElements(workspaceInfo, { includeContainers: true, + includeDeploymentNodes: false, }).map((elm) => ({ name: `${labelElementByTags(elm.tags)} ${ elm.systemName ? `${elm.systemName}/` : "" @@ -27,26 +28,18 @@ const generator: GeneratorDefinition = { }, })); - const { element } = await prompt({ - type: "list", - name: "element", + const element = await select({ message: "Element:", choices: systemElements, }); - const relationships = await getRelationships( + const relationships = await addRelationshipsToElement( element.elementName, workspaceInfo, - prompt, { includeContainers: element.systemName ? element.systemName - : false, - validate: (input) => { - console.log("🦊", "input", input); - - return true; - }, + : undefined, }, ); diff --git a/lib/utils/labels.ts b/lib/utils/labels.ts index 7ec410c..5bbb0d8 100644 --- a/lib/utils/labels.ts +++ b/lib/utils/labels.ts @@ -4,6 +4,7 @@ export enum Labels { External = "⬜️", Person = "👤", System = "🟦", + DeploymentNode = "🟧", Relationship = "⇢ ", View = "🔳", } @@ -13,6 +14,7 @@ export const labelElementByTags = (tags: string): string => { if (tag === "Person") return Labels.Person; if (tag === "External") return Labels.External; if (tag === "Container") return Labels.Container; + if (tag === "Deployment Node") return Labels.DeploymentNode; } return Labels.System; @@ -23,6 +25,7 @@ export const elementTypeByTags = (tags: string): string => { if (tag === "Person") return "Person"; if (tag === "External") return "External"; if (tag === "Container") return "Container"; + if (tag === "Deployment Node") return "DeploymentNode"; } return "System"; diff --git a/lib/utils/questions/system.ts b/lib/utils/questions/system.ts index 208a344..f2e20b0 100644 --- a/lib/utils/questions/system.ts +++ b/lib/utils/questions/system.ts @@ -14,18 +14,29 @@ type GetSystemQuestionOptions = { type SoftwareElement = StructurizrWorkspace["model"]["people"][number]; type SoftwareSystem = StructurizrWorkspace["model"]["softwareSystems"][number]; +type DeploymentNode = StructurizrWorkspace["model"]["deploymentNodes"][number]; type GetAllSystemElementsOptions = { includeContainers?: boolean; + includeDeploymentNodes?: boolean; }; +// TODO: Test filtering logic export function getAllSystemElements( workspaceInfo: StructurizrWorkspace | undefined, - { includeContainers = true }: GetAllSystemElementsOptions = {}, -): (SoftwareElement & { systemName?: string })[] { + { + includeContainers = true, + includeDeploymentNodes = false, + }: GetAllSystemElementsOptions = {}, +): ((SoftwareElement | DeploymentNode) & { systemName?: string })[] { if (!workspaceInfo) return []; const systemElements = Object.values(workspaceInfo.model) .flat() + .filter((elm) => + !includeDeploymentNodes + ? !elm.tags.split(",").includes("Deployment Node") + : true, + ) .flatMap((elm) => { const sysElm = elm as SoftwareSystem; if (includeContainers && sysElm.containers) { From fd30ae095d37f9f406f377ec33f3ca7b0c1a9b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Zorro?= Date: Wed, 17 Jul 2024 17:22:17 -0500 Subject: [PATCH 9/9] refactor(generators): added type safety and removed inquirer@9 dependency --- bun.lockb | Bin 114840 -> 106008 bytes lib/generators/constant.ts | 9 +- lib/generators/container.ts | 30 +++- lib/generators/external-system.ts | 22 ++- lib/generators/person.ts | 20 ++- lib/generators/relationship.ts | 20 ++- lib/generators/system.ts | 24 ++- lib/generators/view.ts | 18 +- lib/generators/workspace.ts | 30 ++-- lib/main.ts | 40 ++--- lib/utils/actions/add-many.ts | 9 +- lib/utils/actions/add.ts | 9 +- lib/utils/actions/append.ts | 9 +- lib/utils/actions/index.ts | 10 +- lib/utils/actions/utils.ts | 13 +- lib/utils/generator.test.ts | 84 ++++----- lib/utils/generator.ts | 45 ++--- lib/utils/questions/relationships.test.ts | 202 ---------------------- lib/utils/questions/relationships.ts | 131 +------------- lib/utils/questions/system.ts | 64 ------- lib/utils/questions/validators.test.ts | 52 ++---- lib/utils/questions/validators.ts | 32 ++-- package.json | 1 - 23 files changed, 260 insertions(+), 614 deletions(-) delete mode 100644 lib/utils/questions/relationships.test.ts diff --git a/bun.lockb b/bun.lockb index cde599f633ec348ba55e387c69ad74a532dd622e..a85907287fda82f58f4cf9c1934d8376b4998fe4 100755 GIT binary patch delta 18812 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 diff --git a/lib/generators/constant.ts b/lib/generators/constant.ts index 5789d81..36521ff 100644 --- a/lib/generators/constant.ts +++ b/lib/generators/constant.ts @@ -3,7 +3,12 @@ import type { AppendAction } from "../utils/actions"; import type { GeneratorDefinition, QuestionsObject } from "../utils/generator"; import { stringEmpty } from "../utils/questions/validators"; -const generator: GeneratorDefinition = { +type ConstantAnswers = { + constantName: string; + constantValue: string; +}; + +const generator: GeneratorDefinition = { name: "Constant", description: "Create a new workspace constant", questions: { @@ -27,7 +32,7 @@ const generator: GeneratorDefinition = { path: "architecture/workspace.dsl", pattern: /# Constants/, templateFile: "templates/constant.hbs", - } as AppendAction, + } as AppendAction, ], }; diff --git a/lib/generators/container.ts b/lib/generators/container.ts index 24dd5ff..e8cf770 100644 --- a/lib/generators/container.ts +++ b/lib/generators/container.ts @@ -5,7 +5,10 @@ import { kebabCase, pascalCase } from "change-case"; import type { AddAction, AppendAction } from "../utils/actions"; import type { GeneratorDefinition } from "../utils/generator"; import { removeSpaces } from "../utils/handlebars"; -import { addRelationshipsToElement } from "../utils/questions/relationships"; +import { + type Relationship, + addRelationshipsToElement, +} from "../utils/questions/relationships"; import { resolveSystemQuestion } from "../utils/questions/system"; import { chainValidators, @@ -15,10 +18,21 @@ import { } from "../utils/questions/validators"; import { getWorkspaceJson, getWorkspacePath } from "../utils/workspace"; -const generator: GeneratorDefinition = { +type ContainerAnswers = { + systemName: string; + elementName: string; + containerDescription: string; + containerType: string; + containerTechnology: string; + includeTabs: string; + includeSource: string; + relationships: Record; +}; + +const generator: GeneratorDefinition = { name: "Container", description: "Create a new system container", - questions: async (_, generator) => { + questions: async (generator) => { const workspaceInfo = await getWorkspaceJson( getWorkspacePath(generator.destPath), ); @@ -31,7 +45,7 @@ const generator: GeneratorDefinition = { const elementName = await input({ message: "Container Name:", required: true, - validate: chainValidators( + validate: chainValidators<{ systemName: string }>( stringEmpty, duplicatedSystemName, validateDuplicatedElements(workspaceInfo), @@ -97,7 +111,7 @@ const generator: GeneratorDefinition = { path: "architecture/containers/{{kebabCase systemName}}/{{kebabCase elementName}}.dsl", skipIfExists: true, templateFile: "templates/containers/container.hbs", - } as AddAction, + } as AddAction, { type: "append", path: "architecture/relationships/_system.dsl", @@ -125,7 +139,7 @@ const generator: GeneratorDefinition = { }, pattern: /.*\n!include.*/, templateFile: "templates/include.hbs", - } as AppendAction, + } as AppendAction, { type: "append", path: "architecture/views/{{kebabCase systemName}}.dsl", @@ -150,13 +164,13 @@ const generator: GeneratorDefinition = { ); }, templateFile: "templates/views/container.hbs", - } as AppendAction, + } as AppendAction, { type: "append", createIfNotExists: true, path: "architecture/relationships/{{kebabCase systemName}}.dsl", templateFile: "templates/relationships/multiple.hbs", - } as AppendAction, + } as AppendAction, ], }; diff --git a/lib/generators/external-system.ts b/lib/generators/external-system.ts index f64c956..b70d79c 100644 --- a/lib/generators/external-system.ts +++ b/lib/generators/external-system.ts @@ -3,6 +3,7 @@ import type { AppendAction } from "../utils/actions"; import { whenFileExists } from "../utils/actions/utils"; import type { GeneratorDefinition } from "../utils/generator"; import { + type Relationship, addRelationshipsToElement, defaultParser, resolveRelationshipForElement, @@ -16,10 +17,19 @@ import { } from "../utils/questions/validators"; import { getWorkspaceJson, getWorkspacePath } from "../utils/workspace"; -const generator: GeneratorDefinition = { +type ExternalSystemAnswers = { + systemName: string; + elementName: string; + extSystemDescription: string; + includeSource: string; + includeTabs: string; + relationships: Record; +}; + +const generator: GeneratorDefinition = { name: "External System", description: "Create a new external system", - questions: async (_, generator) => { + questions: async (generator) => { const workspaceInfo = await getWorkspaceJson( getWorkspacePath(generator.destPath), ); @@ -31,7 +41,7 @@ const generator: GeneratorDefinition = { const elementName = await input({ message: "External system name:", required: true, - validate: chainValidators( + validate: chainValidators<{ systemName: string }>( stringEmpty, duplicatedSystemName, validateDuplicatedElements(workspaceInfo), @@ -87,19 +97,19 @@ const generator: GeneratorDefinition = { path: "architecture/workspace.dsl", pattern: /# Relationships/, templateFile: "templates/include.hbs", - } as AppendAction, + } as AppendAction, { createIfNotExists: true, type: "append", path: "architecture/systems/_external.dsl", templateFile: "templates/system/external.hbs", - } as AppendAction, + } as AppendAction, { createIfNotExists: true, type: "append", path: "architecture/relationships/_external.dsl", templateFile: "templates/relationships/multiple.hbs", - } as AppendAction, + } as AppendAction, ], }; diff --git a/lib/generators/person.ts b/lib/generators/person.ts index 086a753..c65e4c4 100644 --- a/lib/generators/person.ts +++ b/lib/generators/person.ts @@ -3,6 +3,7 @@ import type { AppendAction } from "../utils/actions"; import { whenFileExists } from "../utils/actions/utils"; import type { GeneratorDefinition } from "../utils/generator"; import { + type Relationship, addRelationshipsToElement, defaultParser, resolveRelationshipForElement, @@ -15,10 +16,19 @@ import { } from "../utils/questions/validators"; import { getWorkspaceJson, getWorkspacePath } from "../utils/workspace"; -const generator: GeneratorDefinition = { +type PersonAnswers = { + systemName: string; + personDescription: string; + elementName: string; + includeSource: string; + includeTabs: string; + relationships: Record; +}; + +const generator: GeneratorDefinition = { name: "Person", description: "Create a new person (customer, user, etc)", - questions: async (_, generator) => { + questions: async (generator) => { const workspaceInfo = await getWorkspaceJson( getWorkspacePath(generator.destPath), ); @@ -84,19 +94,19 @@ const generator: GeneratorDefinition = { path: "architecture/workspace.dsl", pattern: /# Relationships/, templateFile: "templates/include.hbs", - } as AppendAction, + } as AppendAction, { type: "append", createIfNotExists: true, path: "architecture/systems/_people.dsl", templateFile: "templates/system/person.hbs", - } as AppendAction, + } as AppendAction, { createIfNotExists: true, type: "append", path: "architecture/relationships/_people.dsl", templateFile: "templates/relationships/multiple.hbs", - } as AppendAction, + } as AppendAction, ], }; diff --git a/lib/generators/relationship.ts b/lib/generators/relationship.ts index d2a1a35..cd03f4d 100644 --- a/lib/generators/relationship.ts +++ b/lib/generators/relationship.ts @@ -2,14 +2,24 @@ import { select } from "@inquirer/prompts"; import type { AppendAction } from "../utils/actions"; import type { GeneratorDefinition } from "../utils/generator"; import { elementTypeByTags, labelElementByTags } from "../utils/labels"; -import { addRelationshipsToElement } from "../utils/questions/relationships"; +import { + type Relationship, + addRelationshipsToElement, +} from "../utils/questions/relationships"; import { getAllSystemElements } from "../utils/questions/system"; import { getWorkspaceJson, getWorkspacePath } from "../utils/workspace"; -const generator: GeneratorDefinition = { +type RelationshipAnswers = { + elementName: string; + systemName?: string; + elementType: string; + relationships: Record; +}; + +const generator: GeneratorDefinition = { name: "Relationship", description: "Create a new relationship between elements", - questions: async (_, generator) => { + questions: async (generator) => { const workspaceInfo = await getWorkspaceJson( getWorkspacePath(generator.destPath), ); @@ -64,7 +74,7 @@ const generator: GeneratorDefinition = { path: "architecture/relationships/_{{kebabCase elementType}}.dsl", pattern: /\n.* -> .*\n/, templateFile: "templates/relationships/multiple.hbs", - } as AppendAction, + } as AppendAction, { when: (answers) => Boolean(answers.systemName), skip: (answers) => @@ -75,7 +85,7 @@ const generator: GeneratorDefinition = { path: "architecture/relationships/{{kebabCase systemName}}.dsl", pattern: /\n.* -> .*\n/, templateFile: "templates/relationships/multiple.hbs", - } as AppendAction, + } as AppendAction, ], }; diff --git a/lib/generators/system.ts b/lib/generators/system.ts index e147fcc..7687bf0 100644 --- a/lib/generators/system.ts +++ b/lib/generators/system.ts @@ -1,7 +1,10 @@ import { input } from "@inquirer/prompts"; import type { AddAction, AppendAction } from "../utils/actions"; import type { GeneratorDefinition } from "../utils/generator"; -import { addRelationshipsToElement } from "../utils/questions/relationships"; +import { + type Relationship, + addRelationshipsToElement, +} from "../utils/questions/relationships"; import { chainValidators, stringEmpty, @@ -9,10 +12,17 @@ import { } from "../utils/questions/validators"; import { getWorkspaceJson, getWorkspacePath } from "../utils/workspace"; -const generator: GeneratorDefinition = { +type SystemAnswers = { + systemName: string; + systemDescription: string; + elementName: string; + relationships: Record; +}; + +const generator: GeneratorDefinition = { name: "System", description: "Create a new software system", - questions: async (_, generator) => { + questions: async (generator) => { const workspaceInfo = await getWorkspaceJson( getWorkspacePath(generator.destPath), ); @@ -57,24 +67,24 @@ const generator: GeneratorDefinition = { skipIfExists: true, path: "architecture/systems/{{kebabCase systemName}}.dsl", templateFile: "templates/system/system.hbs", - } as AddAction, + } as AddAction, { type: "add", skipIfExists: true, path: "architecture/containers/{{kebabCase systemName}}/.gitkeep", templateFile: "templates/empty.hbs", - } as AddAction, + } as AddAction, { type: "append", path: "architecture/relationships/_system.dsl", pattern: /\n.* -> .*\n/, templateFile: "templates/relationships/multiple.hbs", - } as AppendAction, + } as AppendAction, { type: "add", path: "architecture/views/{{kebabCase systemName}}.dsl", templateFile: "templates/views/system.hbs", - } as AddAction, + } as AddAction, ], }; diff --git a/lib/generators/view.ts b/lib/generators/view.ts index bfa1410..d9c501c 100644 --- a/lib/generators/view.ts +++ b/lib/generators/view.ts @@ -10,15 +10,23 @@ import { } from "../utils/questions/validators"; import { getWorkspaceJson, getWorkspacePath } from "../utils/workspace"; +type ViewAnswers = { + viewType: string; + viewName: string; + viewDescription: string; + systemName?: string; + instanceDescription?: string; +}; + // TODO: Other types of views // - Dynamic // - Filtered // // - System landscape // // - Deployment -const generator: GeneratorDefinition = { +const generator: GeneratorDefinition = { name: "View", description: "Create a new view", - questions: async (_, generator) => { + questions: async (generator) => { const workspaceInfo = await getWorkspaceJson( getWorkspacePath(generator.destPath), ); @@ -75,20 +83,20 @@ const generator: GeneratorDefinition = { type: "add", path: "architecture/views/{{kebabCase viewName}}.dsl", templateFile: "templates/views/deployment.hbs", - } as AddAction, + } as AddAction, { skip: skipUnlessViewType("deployment"), type: "add", path: "architecture/environments/{{kebabCase viewName}}.dsl", templateFile: "templates/environments/deployment.hbs", - } as AddAction, + } as AddAction, { when: whenViewType("landscape"), type: "add", skipIfExists: true, path: "architecture/views/{{kebabCase viewName}}.dsl", templateFile: "templates/views/landscape.hbs", - } as AddAction, + } as AddAction, ], }; diff --git a/lib/generators/workspace.ts b/lib/generators/workspace.ts index 49d55bf..7ca8ef1 100644 --- a/lib/generators/workspace.ts +++ b/lib/generators/workspace.ts @@ -7,7 +7,17 @@ import { stringEmpty } from "../utils/questions/validators"; const globalUserName = await $`git config --global user.name`.text(); const globalUserEmail = await $`git config --global user.email`.text(); -const generator: GeneratorDefinition = { +type WorkspaceAnswers = { + workspaceName: string; + workspaceDescription: string; + systemName: string; + systemDescription: string; + authorName: string; + authorEmail: string; + shouldIncludeTheme: boolean; +}; + +const generator: GeneratorDefinition = { name: "Workspace", description: "Create a new workspace", questions: { @@ -54,49 +64,49 @@ const generator: GeneratorDefinition = { type: "add", path: "architecture/workspace.dsl", templateFile: "templates/workspace.hbs", - } as AddAction, + } as AddAction, { type: "add", path: "architecture/systems/_system.dsl", templateFile: "templates/system/system.hbs", - } as AddAction, + } as AddAction, { type: "add", path: "architecture/containers/{{kebabCase systemName}}/.gitkeep", templateFile: "templates/empty.hbs", - } as AddAction, + } as AddAction, { type: "add", path: "architecture/relationships/_system.dsl", templateFile: "templates/empty.hbs", - } as AddAction, + } as AddAction, { type: "add", path: "architecture/.gitignore", templateFile: "templates/.gitignore", - } as AddAction, + } as AddAction, { type: "add", path: "architecture/.env-arch", templateFile: "templates/.env-arch", - } as AddAction, + } as AddAction, { type: "addMany", destination: "architecture", templateFiles: "templates/scripts/**/*.sh", skipIfExists: true, filePermissions: "744", - } as AddManyAction, + } as AddManyAction, { type: "addMany", destination: "architecture", templateFiles: "templates/**/.gitkeep", - } as AddManyAction, + } as AddManyAction, { type: "add", path: "architecture/views/{{kebabCase systemName}}.dsl", templateFile: "templates/views/system.hbs", - } as AddAction, + } as AddAction, ], }; diff --git a/lib/main.ts b/lib/main.ts index d8383b6..e5b7280 100644 --- a/lib/main.ts +++ b/lib/main.ts @@ -1,9 +1,8 @@ import { relative, resolve } from "node:path"; +import { select } from "@inquirer/prompts"; import { $ } from "bun"; import chalk from "chalk"; import { capitalCase } from "change-case"; -import inquirer from "inquirer"; -import type { Answers } from "inquirer"; import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import pkg from "../package.json"; @@ -43,8 +42,6 @@ Create a Structurizr DSL scaffolding in seconds! `), ); -// TODO: Remove -const prompt = inquirer.createPromptModule(); const destPath = resolve(process.cwd(), args.dest); const workspacePath = getWorkspacePath(destPath); @@ -74,7 +71,7 @@ Let's create a new one by answering the questions below. destPath, }; - await createGenerator(prompt, generator); + await createGenerator(generator); await exportWorkspace( relative(process.cwd(), destPath) || process.cwd(), ); @@ -91,30 +88,27 @@ console.log( )}\n`, ); -const mainPrompt = inquirer.createPromptModule(); -const generate = await mainPrompt<{ element: GeneratorDefinition }>([ - { - name: "element", - message: "Create a new element:", - type: "list", - choices: Object.values(otherGenerators) - .map((g) => ({ - name: `${labelElementByName(g.name)} ${g.name}`, - value: g, - })) - .toReversed() - .toSorted(), - }, -]); +const element = await select({ + message: "Create a new element:", + choices: Object.values(otherGenerators) + .map((g) => ({ + name: `${labelElementByName(g.name)} ${g.name}`, + value: g, + })) + .toReversed() + .toSorted(), +}); + +type GeneratorAnswers = GetAnswers; try { - const generator: Generator> = { - ...generate.element, + const generator: Generator = { + ...(element as GeneratorDefinition), templates, destPath, }; - await createGenerator(prompt, generator); + await createGenerator(generator); await exportWorkspace(relative(process.cwd(), workspacePath)); process.exit(0); } catch (err) { diff --git a/lib/utils/actions/add-many.ts b/lib/utils/actions/add-many.ts index 1e684a0..0b4c782 100644 --- a/lib/utils/actions/add-many.ts +++ b/lib/utils/actions/add-many.ts @@ -1,12 +1,11 @@ import { join } from "node:path"; import { Glob } from "bun"; import chalk from "chalk"; -import type { Answers } from "inquirer"; import type { BaseAction, ExtendedAction } from "."; import { ActionTypes, add } from "."; import { compileSource } from "../handlebars"; -export type AddManyAction = BaseAction & { +export type AddManyAction> = BaseAction & { type: ActionTypes.AddMany; destination: string; templateFiles: string; @@ -14,8 +13,8 @@ export type AddManyAction = BaseAction & { skipIfExists?: boolean; }; -export async function addMany( - options: ExtendedAction & AddManyAction, +export async function addMany>( + options: ExtendedAction & AddManyAction, answers: A, ): Promise { const { @@ -44,7 +43,7 @@ export async function addMany( return false; } - const compiledOpts = compileSource(opts, answers); + const compiledOpts = compileSource>(opts, answers); const pattern = new Glob(compiledOpts.templateFiles); const filesToCreate = []; diff --git a/lib/utils/actions/add.ts b/lib/utils/actions/add.ts index 86e26e6..78eccf9 100644 --- a/lib/utils/actions/add.ts +++ b/lib/utils/actions/add.ts @@ -1,11 +1,10 @@ import { join, relative, resolve } from "node:path"; import { $, file, write } from "bun"; import chalk from "chalk"; -import type { Answers } from "inquirer"; import type { ActionTypes, BaseAction, ExtendedAction } from "."; import { compileSource, compileTemplateFile } from "../handlebars"; -export type AddAction = BaseAction & { +export type AddAction> = BaseAction & { type: ActionTypes.Add; templateFile: string; path: string; @@ -13,8 +12,8 @@ export type AddAction = BaseAction & { skipIfExists?: boolean; }; -export async function add( - options: ExtendedAction & AddAction, +export async function add>( + options: ExtendedAction & AddAction, answers: A, ): Promise { const { @@ -43,7 +42,7 @@ export async function add( return false; } - const compiledOpts = compileSource(opts, answers); + const compiledOpts = compileSource>(opts, answers); const targetFile = file(resolve(rootPath, compiledOpts.path)); const relativePath = relative( diff --git a/lib/utils/actions/append.ts b/lib/utils/actions/append.ts index b95ab4f..a6e236e 100644 --- a/lib/utils/actions/append.ts +++ b/lib/utils/actions/append.ts @@ -2,11 +2,10 @@ import { access } from "node:fs/promises"; import { join, relative, resolve } from "node:path"; import { $, file, write } from "bun"; import chalk from "chalk"; -import type { Answers } from "inquirer"; import type { ActionTypes, BaseAction, ExtendedAction } from "."; import { compileSource, compileTemplateFile } from "../handlebars"; -export type AppendAction = BaseAction & { +export type AppendAction> = BaseAction & { type: ActionTypes.Append; templateFile: string; path: string; @@ -15,8 +14,8 @@ export type AppendAction = BaseAction & { pattern?: RegExp; }; -export async function append( - options: ExtendedAction & AppendAction, +export async function append>( + options: ExtendedAction & AppendAction, answers: A, ): Promise { const { @@ -47,7 +46,7 @@ export async function append( return false; } - const compiledOpts = compileSource(opts, answers); + const compiledOpts = compileSource>(opts, answers); const targetFilePath = resolve(rootPath, compiledOpts.path); const relativePath = relative(process.cwd(), targetFilePath); const targetFile = file(targetFilePath); diff --git a/lib/utils/actions/index.ts b/lib/utils/actions/index.ts index 888e06a..a06e260 100644 --- a/lib/utils/actions/index.ts +++ b/lib/utils/actions/index.ts @@ -1,19 +1,17 @@ -import type { Answers } from "inquirer"; - export enum ActionTypes { Add = "add", AddMany = "addMany", Append = "append", } -declare function whenOrSkip( +declare function whenOrSkip>( answers: A, rootPath: string, ): boolean | string | Promise; -export type BaseAction = { - when?: typeof whenOrSkip; - skip?: typeof whenOrSkip; +export type BaseAction> = { + when?: typeof whenOrSkip; + skip?: typeof whenOrSkip; }; export type ExtendedAction = { diff --git a/lib/utils/actions/utils.ts b/lib/utils/actions/utils.ts index c993f4f..e6ceaf1 100644 --- a/lib/utils/actions/utils.ts +++ b/lib/utils/actions/utils.ts @@ -1,12 +1,15 @@ import { resolve } from "node:path"; import { file } from "bun"; -import type { Answers } from "inquirer"; -export const skipUnlessViewType = (type: string) => (answer: Answers) => - answer.viewType !== type && `View type "${type}" not selected.`; +export const skipUnlessViewType = + >(type: string) => + (answer: A) => + answer.viewType !== type && `View type "${type}" not selected.`; -export const whenViewType = (type: string) => (answer: Answers) => - answer.viewType === type; +export const whenViewType = + >(type: string) => + (answer: A) => + answer.viewType === type; export const whenFileExists = async ( filePath: string, diff --git a/lib/utils/generator.test.ts b/lib/utils/generator.test.ts index 75ae34d..4a95a7c 100644 --- a/lib/utils/generator.test.ts +++ b/lib/utils/generator.test.ts @@ -1,5 +1,5 @@ import { describe, expect, mock, test } from "bun:test"; -import type { PromptModule } from "inquirer"; +import { CancelablePromise } from "@inquirer/type"; import templates from "../templates/bundle"; import type { AddAction } from "./actions"; import type { Generator } from "./generator"; @@ -8,10 +8,15 @@ import { createGenerator } from "./generator"; describe("generator", () => { describe("createGenerator", () => { test("should create generator and execute actions", async () => { - const prompt = mock(() => Promise.resolve({ answer: 123 })); + const prompt = mock( + () => + new CancelablePromise((resolve) => resolve("123")), + ); const execute = mock(); - const questions = [{ type: "input", name: "question" }]; - const actions = [{ type: "add" } as AddAction]; + const questions = { + answer: prompt, + }; + const actions = [{ type: "add" } as AddAction<{ answer: string }>]; const definition: Generator<{ answer: string }> = { name: "Test", @@ -23,38 +28,34 @@ describe("generator", () => { }; expect( - async () => - await createGenerator( - prompt as unknown as PromptModule, - definition, - execute, - ), + async () => await createGenerator(definition, execute), ).not.toThrow(); - expect(prompt).toHaveBeenCalledWith(questions); expect(execute).toHaveBeenCalled(); expect(execute.mock.lastCall).toEqual([ expect.objectContaining({ ...actions[0], }), - { answer: 123 }, + { answer: "123" }, ]); }); test("should create generator from function questions", async () => { - const prompt = mock(() => Promise.resolve({ answer: 123 })); + const prompt = mock( + () => + new CancelablePromise((resolve) => resolve("123")), + ); const execute = mock(); - const questionsArr = [{ type: "input", name: "question" }]; - const questions = ( - promptMock: ( - questions: unknown[], - ) => Promise<{ answer: string }>, - ) => { - return promptMock(questionsArr); + const questions = async () => { + return { + question: await prompt(), + }; }; - const actions = [{ type: "add" } as AddAction]; + const actions = [ + { type: "add" } as AddAction<{ question: string }>, + ]; - const definition: Generator<{ answer: string }> = { + const definition: Generator<{ question: string }> = { name: "Test", description: "Test Generator", destPath: `${import.meta.dirname}/workspace.dsl`, @@ -64,21 +65,16 @@ describe("generator", () => { }; expect( - async () => - await createGenerator( - prompt as unknown as PromptModule, - definition, - execute, - ), + async () => await createGenerator(definition, execute), ).not.toThrow(); - expect(prompt).toHaveBeenCalledWith(questionsArr); + expect(prompt).toHaveBeenCalled(); expect(execute).toHaveBeenCalled(); expect(execute.mock.lastCall).toEqual([ expect.objectContaining({ ...actions[0], }), - { answer: 123 }, + { question: "123" }, ]); }); @@ -93,38 +89,33 @@ describe("generator", () => { setTimeout(() => done(true), randomTime); }), ); - const questionsArr = [{ type: "input", name: "question" }]; - const questions = ( - promptMock: ( - questions: unknown[], - ) => Promise<{ answer: string }>, - ) => { - return promptMock(questionsArr); + const questions = async () => { + return await prompt(); }; const actions = [ { type: "add", path: "path1", templateFile: "templateFile", - } as AddAction, + } as AddAction<{ answer: number }>, { type: "add", path: "path2", templateFile: "templateFile", - } as AddAction, + } as AddAction<{ answer: number }>, { type: "add", path: "path3", templateFile: "templateFile", - } as AddAction, + } as AddAction<{ answer: number }>, { type: "add", path: "path4", templateFile: "templateFile", - } as AddAction, + } as AddAction<{ answer: number }>, ]; - const definition: Generator<{ answer: string }> = { + const definition: Generator<{ answer: number }> = { name: "Test", description: "Test Generator", destPath: `${import.meta.dirname}/workspace.dsl`, @@ -134,15 +125,10 @@ describe("generator", () => { }; expect( - async () => - await createGenerator( - prompt as unknown as PromptModule, - definition, - execute, - ), + async () => await createGenerator(definition, execute), ).not.toThrow(); - expect(prompt).toHaveBeenCalledWith(questionsArr); + expect(prompt).toHaveBeenCalled(); expect(execute).toHaveBeenCalled(); expect(execute.mock.calls.map(([args]) => args.path)).toEqual([ "path1", diff --git a/lib/utils/generator.ts b/lib/utils/generator.ts index 807dddb..b379912 100644 --- a/lib/utils/generator.ts +++ b/lib/utils/generator.ts @@ -1,6 +1,5 @@ import type { CancelablePromise } from "@inquirer/type"; import chalk from "chalk"; -import type { PromptModule, QuestionCollection } from "inquirer"; import type { AddAction, AddManyAction, @@ -21,11 +20,8 @@ export type GeneratorDefinition< > = { name: string; description: string; - questions: - | QuestionCollection - | ((prompt: PromptModule, generator: Generator) => Promise) - | QuestionsObject; - actions: (AddAction | AddManyAction | AppendAction)[]; + questions: ((generator: Generator) => Promise) | QuestionsObject; + actions: (AddAction | AddManyAction | AppendAction)[]; }; export type Generator> = @@ -39,7 +35,8 @@ export type GetAnswers = Type extends GeneratorDefinition : null; async function executeAction>( - action: ExtendedAction & (AddAction | AddManyAction | AppendAction), + action: ExtendedAction & + (AddAction | AddManyAction | AppendAction), answers: A, ): Promise { switch (action.type) { @@ -59,7 +56,6 @@ async function executeAction>( } export async function createGenerator>( - prompt: PromptModule, generator: Generator, execute = executeAction, ): Promise { @@ -67,28 +63,25 @@ export async function createGenerator>( const answers = generator.questions instanceof Function - ? // TODO: Remove "prompt" argument from generator.questions - await generator.questions(prompt, generator) - : Array.isArray(generator.questions) - ? await prompt(generator.questions) - : await Object.entries( - generator.questions as QuestionsObject, - ).reduce( - async (answers, [name, prompt]) => { - const acc = await answers; + ? await generator.questions(generator) + : ((await Object.entries( + generator.questions as QuestionsObject, + ).reduce( + async (answers, [name, prompt]) => { + const acc = await answers; - const answer = await prompt?.(); + const answer = await prompt?.(); - return { - ...acc, - [name]: answer, - }; - }, - Promise.resolve({} as Record), - ); + return { + ...acc, + [name]: answer, + }; + }, + Promise.resolve({} as Record), + )) as A); for await (const action of generator.actions) { - await execute( + await execute( { ...action, rootPath: generator.destPath, diff --git a/lib/utils/questions/relationships.test.ts b/lib/utils/questions/relationships.test.ts deleted file mode 100644 index 4f0719a..0000000 --- a/lib/utils/questions/relationships.test.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { describe, expect, mock, test } from "bun:test"; -import { Separator } from "@inquirer/prompts"; -import type { PromptModule } from "inquirer"; -import type { StructurizrWorkspace } from "../workspace"; -import { getRelationships } from "./relationships"; - -describe("relationships", () => { - describe("getRelationships", () => { - type Model = StructurizrWorkspace["model"]; - type SoftwareElement = Model["people"][number]; - - test("should return empty when no workspaceInfo passed", async () => { - const relationships = await getRelationships( - "someElement", - undefined, - mock() as unknown as PromptModule, - ); - - expect(relationships).toEqual({}); - }); - test("should return empty when no system elements found", async () => { - const prompt = mock().mockResolvedValue({ relationships: [] }); - - const relationships = await getRelationships( - "someElement", - { - model: { - people: [] as SoftwareElement[], - softwareSystems: [] as SoftwareElement[], - deploymentNodes: [ - { - id: "123", - tags: "Element,Deployment Node", - name: "SomeDeploymentNode", - }, - ] as SoftwareElement[], - }, - } as StructurizrWorkspace, - prompt as unknown as PromptModule, - ); - - expect(relationships).toEqual({}); - expect(prompt).not.toHaveBeenCalled(); - }); - - test("should return empty when no elements flagged for relationship", async () => { - const prompt = mock().mockResolvedValue({ relationships: [] }); - - const relationships = await getRelationships( - "someElement", - { - model: { - people: [ - { - id: "123", - tags: "Element,Person", - name: "SomePerson", - }, - ] as SoftwareElement[], - softwareSystems: [ - { - id: "123", - tags: "Element,SoftwareSystem", - name: "SomeSystem", - }, - ] as SoftwareElement[], - deploymentNodes: [ - { - id: "123", - tags: "Element,Deployment Node", - name: "SomeDeploymentNode", - }, - ] as SoftwareElement[], - }, - } as StructurizrWorkspace, - prompt as unknown as PromptModule, - ); - - expect(prompt).toHaveBeenCalledWith({ - type: "checkbox", - name: "relationships", - message: expect.any(String), - choices: [ - expect.any(Separator), - { - name: "🟦 SomeSystem", - value: "SomeSystem", - }, - expect.any(Separator), - { - name: "👤 SomePerson", - value: "SomePerson", - }, - ], - when: expect.any(Function), - validate: expect.any(Function), - }); - - expect(relationships).toEqual({}); - }); - - test("should get relationships correctly", async () => { - const prompt = mock() - .mockResolvedValueOnce({ - relationships: ["SomePerson", "SomeSystem"], - }) - .mockResolvedValueOnce({ - SomePerson_relationshipType: "outgoing", - SomePerson_relationship: "Consumes", - SomePerson_technology: "Web/HTTP", - SomeSystem_relationshipType: "outgoing", - SomeSystem_relationship: "Consumes", - SomeSystem_technology: "Web/HTTP", - }); - - const relationships = await getRelationships( - "someElement", - { - model: { - people: [ - { - id: "123", - tags: "Element,Person", - name: "SomePerson", - }, - ] as SoftwareElement[], - softwareSystems: [ - { - id: "123", - tags: "Element,SoftwareSystem", - name: "SomeSystem", - }, - ] as SoftwareElement[], - deploymentNodes: [ - { - id: "123", - tags: "Element,Deployment Node", - name: "SomeDeploymentNode", - }, - ] as SoftwareElement[], - }, - } as StructurizrWorkspace, - prompt as unknown as PromptModule, - ); - - expect(prompt).toHaveBeenCalledTimes(2); - expect(prompt).toHaveBeenLastCalledWith([ - { - type: "list", - name: "SomePerson_relationshipType", - message: expect.any(String), - choices: expect.any(Array), - default: expect.any(String), - }, - { - type: "input", - name: "SomePerson_relationship", - message: expect.any(String), - default: expect.any(String), - }, - { - type: "input", - name: "SomePerson_technology", - message: expect.any(String), - default: expect.any(String), - }, - { - type: "list", - name: "SomeSystem_relationshipType", - message: expect.any(String), - choices: expect.any(Array), - default: expect.any(String), - }, - { - type: "input", - name: "SomeSystem_relationship", - message: expect.any(String), - default: expect.any(String), - }, - { - type: "input", - name: "SomeSystem_technology", - message: expect.any(String), - default: expect.any(String), - }, - ]); - - expect(relationships).toEqual({ - SomePerson: { - relationshipType: "outgoing", - relationship: "Consumes", - technology: "Web/HTTP", - }, - SomeSystem: { - relationshipType: "outgoing", - relationship: "Consumes", - technology: "Web/HTTP", - }, - }); - }); - }); -}); diff --git a/lib/utils/questions/relationships.ts b/lib/utils/questions/relationships.ts index 0f5dd0d..b1d70fe 100644 --- a/lib/utils/questions/relationships.ts +++ b/lib/utils/questions/relationships.ts @@ -1,13 +1,11 @@ import { Separator, checkbox, input, select } from "@inquirer/prompts"; -import type { CancelablePromise } from "@inquirer/type"; import { pascalCase } from "change-case"; -import type { Answers, PromptModule, Question } from "inquirer"; import type { QuestionsObject } from "../generator"; import { removeSpaces } from "../handlebars"; import { labelElementByTags } from "../labels"; import type { StructurizrWorkspace } from "../workspace"; -type Relationship = { +export type Relationship = { relationship: string; relationshipType: string; technology: string; @@ -23,9 +21,8 @@ type Model = StructurizrWorkspace["model"]; type SoftwareElement = Model["people"][number]; type SoftwareSystem = Model["softwareSystems"][number]; -type GetRelationshipsOptions = { - when?: Question["when"]; - validate?: Question["validate"]; +type AddRelationshipOptions = { + validate?: Parameters[0]["validate"]; filterChoices?: ( elm: Separator | { name: string; value: string }, pos: number, @@ -49,50 +46,6 @@ export const defaultParser = (rawRelationshipMap: Record) => { ); }; -// TODO: Remove in favor of resolveRelationshipForElement -export const relationshipsForElement = ( - relationshipName: string, - elementName: string, - { - defaultRelationship = "Interacts with", - defaultRelationshipType = "incoming", - defaultTechnology = "Web/HTTP", - }: RelationshipForElementOptions = {}, -) => { - const elementNamePascalCase = pascalCase(removeSpaces(relationshipName)); - - return [ - { - type: "list", - name: `${elementNamePascalCase}_relationshipType`, - message: `Relationship type for ${relationshipName}`, - choices: [ - { - name: `outgoing (${elementName} → ${relationshipName})`, - value: "outgoing", - }, - { - name: `incoming (${relationshipName} → ${elementName})`, - value: "incoming", - }, - ], - default: defaultRelationshipType, - }, - { - type: "input", - name: `${elementNamePascalCase}_relationship`, - message: `Relationship with ${relationshipName}:`, - default: defaultRelationship, - }, - { - type: "input", - name: `${elementNamePascalCase}_technology`, - message: "Technology:", - default: defaultTechnology, - }, - ]; -}; - const resolveRelationshipPromises = async ( relationshipPromises: QuestionsObject, ): Promise> => { @@ -177,82 +130,6 @@ const separator = ( return maybeSeparator; }; -// TODO: Remove in favor of addRelationshipsToElement -export async function getRelationships( - elementName: string, - workspaceInfo: StructurizrWorkspace | undefined, - prompt: PromptModule, - { - when = () => true, - validate = () => true, - filterChoices = () => true, - parse = defaultParser, - message = "Relates to elements:", - defaultRelationship = "Interacts with", - defaultRelationshipType = "outgoing", - defaultTechnology = "Web/HTTP", - includeContainers, - }: GetRelationshipsOptions = {}, -): Promise> { - if (!workspaceInfo) return {}; - - const softwareSystems = workspaceInfo.model?.softwareSystems ?? []; - const people = workspaceInfo.model?.people ?? []; - const containers = includeContainers - ? findSystemContainers( - includeContainers, - workspaceInfo.model?.softwareSystems, - ) - : []; - - const systemElements = ( - [ - separator(`Containers (${includeContainers})`, containers), - ...containers, - separator("Systems", softwareSystems), - ...softwareSystems, - separator("People", people), - ...people, - ] as (SoftwareElement | Separator)[] - ) - .flat() - .map((elm) => - elm instanceof Separator - ? elm - : { - name: `${labelElementByTags(elm.tags)} ${elm.name}`, - value: elm.name, - }, - ) - .filter(filterChoices); - - if (!systemElements.filter((elm) => !(elm instanceof Separator)).length) - return {}; - - const { relationships } = await prompt({ - type: "checkbox", - name: "relationships", - message, - choices: systemElements, - when, - validate, - }); - - if (!relationships.length) return {}; - - const relationshipQuestions = relationships.flatMap((name: string) => { - return relationshipsForElement(name, elementName, { - defaultRelationship, - defaultRelationshipType, - defaultTechnology, - }); - }); - - const relationshipMap = await prompt(relationshipQuestions); - - return parse(relationshipMap); -} - export async function addRelationshipsToElement( elementName: string, workspaceInfo: StructurizrWorkspace | undefined, @@ -265,7 +142,7 @@ export async function addRelationshipsToElement( defaultRelationshipType = "outgoing", defaultTechnology = "Web/HTTP", includeContainers, - }: GetRelationshipsOptions = {}, + }: AddRelationshipOptions = {}, ): Promise> { if (!workspaceInfo) return {}; diff --git a/lib/utils/questions/system.ts b/lib/utils/questions/system.ts index f2e20b0..cf1a8f6 100644 --- a/lib/utils/questions/system.ts +++ b/lib/utils/questions/system.ts @@ -3,15 +3,9 @@ import { resolve } from "node:path"; import { input, select } from "@inquirer/prompts"; import { CancelablePromise } from "@inquirer/type"; import { kebabCase } from "change-case"; -import type { Answers, AsyncDynamicQuestionProperty, Question } from "inquirer"; import { getWorkspacePath } from "../workspace"; import type { StructurizrWorkspace } from "../workspace"; -type GetSystemQuestionOptions = { - when?: AsyncDynamicQuestionProperty; - message?: string; -}; - type SoftwareElement = StructurizrWorkspace["model"]["people"][number]; type SoftwareSystem = StructurizrWorkspace["model"]["softwareSystems"][number]; type DeploymentNode = StructurizrWorkspace["model"]["deploymentNodes"][number]; @@ -55,64 +49,6 @@ export function getAllSystemElements( return systemElements; } -// TODO: Remove in favor of resolveSystemQuestion -export async function getSystemQuestion( - workspace: string | StructurizrWorkspace, - { - when = () => true, - message = "Relates to system:", - }: GetSystemQuestionOptions = {}, -): Promise { - const workspaceInfo = typeof workspace !== "string" && workspace; - - if (workspaceInfo) { - const systems = (workspaceInfo.model?.softwareSystems ?? []) - .filter((system) => !system.tags.split(",").includes("External")) - .map((system) => system.name); - - const systemQuestion = { - type: "list", - name: "systemName", - message, - choices: systems, - when, - }; - - return systemQuestion; - } - - const workspacePath = typeof workspace === "string" && workspace; - if (!workspacePath) return {}; - - const workspaceFolder = getWorkspacePath(workspacePath); - - const systemQuestion: Question = { - type: "input", - name: "systemName", - message, - when, - validate: (input, answers) => { - if (!answers) return true; - - answers.systemName = input; - - if (workspaceFolder) { - const systemPath = resolve( - workspaceFolder, - `containers/${kebabCase(input)}`, - ); - if (existsSync(systemPath)) return true; - } - - throw new Error( - `System "${input}" does not exist in the workspace.`, - ); - }, - }; - - return systemQuestion; -} - export function resolveSystemQuestion( workspace: string | StructurizrWorkspace, options: { message: string } = { diff --git a/lib/utils/questions/validators.test.ts b/lib/utils/questions/validators.test.ts index 63261f5..dd29905 100644 --- a/lib/utils/questions/validators.test.ts +++ b/lib/utils/questions/validators.test.ts @@ -10,47 +10,27 @@ import { describe("validators", () => { describe("chainValidators", () => { test("should chain validators correctly", async () => { - const validate = chainValidators( - (input) => input.size > 0, - async (input) => input.weight > 0 || "Error message", - (input) => input.height > 0, - (input, answers) => answers?.test === input.test, - )({ test: "test" }); + const validate = chainValidators<{ + size?: number; + weight?: number; + height?: number; + test?: string; + }>( + (input) => input.length > 0, + async (input) => input.endsWith("test") || "Error message", + (input) => input.startsWith("test"), + (input, answers) => answers?.test === input, + )({ test: "testtest" }); - const response1 = await validate?.({ - size: 1, - weight: 1, - height: 1, - test: "test", - }); + const response1 = await validate?.("testtest"); expect(response1).toBeTrue(); - const response2 = await validate?.({ - size: 1, - weight: 1, - height: 0, - test: "test", - }); + const response2 = await validate?.("endsWith_test"); expect(response2).toBeFalse(); - const response3 = await validate?.({ - size: 1, - weight: 0, - height: 1, - test: "test", - }); + const response3 = await validate?.("testable"); expect(response3).toEqual("Error message"); - const response4 = await validate?.({ - size: 0, - weight: 1, - height: 1, - test: "test", - }); + const response4 = await validate?.(""); expect(response4).toBeFalse(); - const response5 = await validate?.({ - size: 1, - weight: 1, - height: 1, - test: "another value", - }); + const response5 = await validate?.("testtosttest"); expect(response5).toBeFalse(); }); }); diff --git a/lib/utils/questions/validators.ts b/lib/utils/questions/validators.ts index eb081ec..7b745dd 100644 --- a/lib/utils/questions/validators.ts +++ b/lib/utils/questions/validators.ts @@ -1,15 +1,23 @@ import { kebabCase, pascalCase } from "change-case"; -import type { Answers, Question } from "inquirer"; import { removeSpaces } from "../handlebars"; import type { StructurizrWorkspace } from "../workspace"; import { getAllSystemElements } from "./system"; -type Validator = Question["validate"]; +type Validator = Record> = ( + input: string, + answers?: A, +) => string | boolean | Promise; export const stringEmpty = (input: string) => input?.length > 0; -export const duplicatedSystemName = (input: string, answers: Answers) => { - if (kebabCase(input) === kebabCase(answers?.systemName)) { +export const duplicatedSystemName = ( + input: string, + answers: A | undefined, +) => { + if ( + answers?.systemName && + kebabCase(input) === kebabCase(answers?.systemName) + ) { return `System name "${input}" already exists`; } @@ -21,9 +29,9 @@ export const validateDuplicatedElements = (input: string) => { if (!workspaceInfo) return true; - const systemElements = getAllSystemElements(workspaceInfo).map((elm) => - pascalCase(removeSpaces(elm.name)), - ); + const systemElements = getAllSystemElements(workspaceInfo, { + includeDeploymentNodes: true, + }).map((elm) => pascalCase(removeSpaces(elm.name))); const elementName = pascalCase(removeSpaces(input)); if (systemElements.includes(elementName)) { return `Element with name "${elementName}" already exists.`; @@ -61,11 +69,11 @@ export const validateDuplicatedViews = return true; }; -export function chainValidators( - ...validators: Validator[] -): (answers?: Record) => Validator { - return (answers = {}) => - async (input: unknown) => { +export function chainValidators< + A extends Record = Record, +>(...validators: Validator[]): (answers?: A) => Validator { + return (answers = {} as A) => + async (input: string) => { for await (const validator of validators) { const validation = await validator?.(input, answers); if (validation !== true) return validation ?? false; diff --git a/package.json b/package.json index 62ac5ac..3437531 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "chalk": "^5.3.0", "change-case": "^5.4.4", "handlebars": "^4.7.8", - "inquirer": "9", "minimist": "^1.2.8", "yargs": "^17.7.2" },