From 113ac7012784408fed2decc07b501cf3af0c902a Mon Sep 17 00:00:00 2001 From: Ayesha <88117894+Ayeshas09@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:18:44 +0500 Subject: [PATCH 1/2] feat: add support for saving and applying sdk customizations (#251) Co-Authored-By: Claude Sonnet 4.6 Co-authored-by: Ali Asghar <75574550+aliasghar98@users.noreply.github.com> Co-authored-by: Asad Ali <14asadali@gmail.com> Co-authored-by: Shield-Jaguar --- .ai/instructions.md | 86 ++ .ai/skills/action.md | 301 +++++++ .ai/skills/command.md | 357 ++++++++ .ai/skills/context.md | 244 ++++++ .ai/skills/event.md | 110 +++ .ai/skills/prompt.md | 312 +++++++ .ai/skills/service.md | 258 ++++++ .ai/skills/value-object.md | 261 ++++++ CLAUDE.md | 17 + package-lock.json | 817 ++++-------------- package.json | 10 +- src/actions/portal/generate.ts | 12 +- src/actions/sdk/generate.ts | 112 ++- src/actions/sdk/merge-source-tree.ts | 95 ++ src/actions/sdk/quickstart.ts | 2 +- src/actions/sdk/save-changes.ts | 123 +++ src/commands/portal/recipe/new.ts | 6 +- src/commands/portal/toc/new.ts | 6 +- src/commands/quickstart.ts | 4 +- src/commands/sdk/generate.ts | 47 +- src/commands/sdk/save-changes.ts | 76 ++ src/hooks/not-found.ts | 2 +- src/infrastructure/file-service.ts | 88 +- src/infrastructure/git-service.ts | 128 +++ src/infrastructure/launcher-service.ts | 16 +- src/infrastructure/service-error.ts | 36 +- src/infrastructure/services/portal-service.ts | 59 +- src/infrastructure/zip-service.ts | 2 +- src/prompts/format.ts | 3 - src/prompts/portal/generate.ts | 12 +- src/prompts/sdk/generate.ts | 41 +- src/prompts/sdk/merge-source-tree.ts | 91 ++ src/prompts/sdk/save-changes.ts | 106 +++ src/types/build-context.ts | 77 +- src/types/build/build.ts | 2 + src/types/events/domain-event.ts | 1 - src/types/events/sdk-changes-saved.ts | 13 + src/types/events/sdk-changes-tracked.ts | 13 + src/types/events/sdk-conflicts-resolved.ts | 13 + src/types/file/directory.ts | 50 +- src/types/file/directoryPath.ts | 2 +- src/types/merge-source-tree-context.ts | 81 ++ src/types/save-changes-context.ts | 59 ++ src/types/sdk-context.ts | 54 +- src/types/spec-context.ts | 1 + src/types/temp-context.ts | 2 +- src/types/versioned-build-context.ts | 37 - 47 files changed, 3415 insertions(+), 830 deletions(-) create mode 100644 .ai/instructions.md create mode 100644 .ai/skills/action.md create mode 100644 .ai/skills/command.md create mode 100644 .ai/skills/context.md create mode 100644 .ai/skills/event.md create mode 100644 .ai/skills/prompt.md create mode 100644 .ai/skills/service.md create mode 100644 .ai/skills/value-object.md create mode 100644 CLAUDE.md create mode 100644 src/actions/sdk/merge-source-tree.ts create mode 100644 src/actions/sdk/save-changes.ts create mode 100644 src/commands/sdk/save-changes.ts create mode 100644 src/infrastructure/git-service.ts create mode 100644 src/prompts/sdk/merge-source-tree.ts create mode 100644 src/prompts/sdk/save-changes.ts create mode 100644 src/types/events/sdk-changes-saved.ts create mode 100644 src/types/events/sdk-changes-tracked.ts create mode 100644 src/types/events/sdk-conflicts-resolved.ts create mode 100644 src/types/merge-source-tree-context.ts create mode 100644 src/types/save-changes-context.ts delete mode 100644 src/types/versioned-build-context.ts diff --git a/.ai/instructions.md b/.ai/instructions.md new file mode 100644 index 00000000..7fb9269b --- /dev/null +++ b/.ai/instructions.md @@ -0,0 +1,86 @@ +# APIMatic CLI — Project Instructions + +This file provides guidance when working with code in this repository. + +## Project Overview + +APIMatic CLI (`@apimatic/cli`) — the official CLI for APIMatic, built on oclif v4 with TypeScript ESM. It provides commands for API spec validation/transformation, SDK generation, and documentation portal management. + +## Common Commands + +```bash +# Build +npm run build # tsc -b → outputs to lib/ + +# Lint +npm run lint # ESLint on src/**/*.{js,ts} +npm run lint:fix # ESLint with --fix --quiet + +# Format +npm run format # Prettier write on src/**/*.{js,ts} + +# Test (all) +npm test # tsx + mocha, runs test/**/*.test.ts + +# Test (single file) +npx tsx node_modules/mocha/bin/_mocha "test/actions/portal/serve.test.ts" --timeout 99999 + +# Run CLI locally +node bin/run.js +``` + +## Architecture — 5-Layer Stack + +``` +Command → Action → Application → Prompts / Infrastructure → Types +``` + +1. **Commands** (`src/commands/`) — oclif `Command` subclasses. Parse flags, build `CommandMetadata`, call `intro()` → `action.execute()` → `outro(result)`. No business logic. +2. **Actions** (`src/actions/`) — One per command. Orchestrate use-case: validate inputs via Context objects, coordinate services, return `ActionResult`. Never throw to Command. +3. **Application** (`src/application/`) — Complex reusable domain algorithms (e.g., TOC generators, recipe generators). Pure transformations: data in → data out. No prompts, no API calls. +4. **Prompts** (`src/prompts/`) — All terminal UI via `@clack/prompts`. One class per command mirroring `actions/`. Uses `withSpinner` for async operations. No business logic. +5. **Infrastructure** (`src/infrastructure/`) — I/O adapters: `FileService`, `ZipService`, `NetworkService`, API services in `services/`. All return `Result` (neverthrow). + +Supporting: **Types** (`src/types/`) for value objects, context objects, and domain events, **client-utils** for auth credential management, **utils** for pure string helpers, **config** for shared Axios instance, **hooks** (`src/hooks/`) for oclif lifecycle hooks (e.g., command-not-found suggestions), **env-info** (`src/infrastructure/env-info.ts`) singleton for CLI version, user-agent string, and base URL resolution. + +## Critical Code Conventions + +- **ESM imports with `.js` extension** — even for `.ts` source files: `import { Foo } from "../../types/file/directoryPath.js"` +- **No raw string paths** — use `DirectoryPath`, `FilePath`, `FileName`, `UrlPath` value objects from `src/types/file/` +- **No `console.log`** — all output through `@clack/prompts` via Prompts classes only +- **Error handling**: Services return `Result` (neverthrow); Actions return `ActionResult` (success/failed/cancelled). No uncaught throws above infrastructure. +- **Prompts delegation** — Actions never call `log.*` directly; every message goes through `this.prompts.*` +- **Temp directories** — always use `withDirPath()` wrapper, never `tmp-promise` directly +- **`authKey` typed as `string | null = null`**, not `undefined` +- **Exit via `outro(result)`** — sets `process.exitCode`; never call `process.exit()` directly +- **`ActionResult` variants** — `success()`, `failed()`, `cancelled()`, `stopped()` (for long-running server commands); exit codes 0 / 1 / 130 respectively +- **Constructor pattern**: `private readonly` properties, `public readonly execute = async (...) => { ... }` +- **`static cmdTxt`** on every Command using `format.cmd(...)` for example rendering +- **Commands use `export default class`** — oclif requires default export; actions, prompts, and services use named exports (`export class`) +- **Static fields use `readonly`** — `static readonly summary`, `static readonly description`, `static readonly cmdTxt` on every Command +- **Topic separator is space** — `apimatic portal generate`, not `apimatic portal:generate` +- **Telemetry** — After `outro(result)`, commands optionally track failures via `result.mapAll(() => {}, async () => { await new TelemetryService(configDir).trackEvent(new SomeFailedEvent(...), shell) }, () => {})`. Event classes extend `DomainEvent` (`src/types/events/`). Only the failure callback is populated; success/cancel are no-ops. + +## Commit Conventions + +Uses [Conventional Commits](https://www.conventionalcommits.org/) enforced by commitlint + husky. Pre-commit runs lint-staged (ESLint + Prettier). + +**Do not commit or push automatically.** Always wait for explicit instruction from the user before running `git commit` or `git push`. + +## Testing + +- **Framework**: mocha + chai (expect style) + sinon + nock + mock-fs +- **Test location**: mirrors source — `test/commands/`, `test/actions/`, `test/application/` +- **HTTP mocking**: nock for API calls +- **Run via tsx** (not ts-node) for ESM compatibility + +## Skills + +Reference these files for scaffolding new code: + +- `.ai/skills/command.md` — Command + Action + Prompts conventions; scaffolding templates and checklists +- `.ai/skills/action.md` — Action class conventions; standard / minimal / delegation variants +- `.ai/skills/context.md` — Context object conventions; output / input / temp / pure variants +- `.ai/skills/prompt.md` — Prompts class conventions; simple / standard / delegation / wizard variants +- `.ai/skills/service.md` — Infrastructure Service conventions; SDK controller / axios-auth / axios-stateless variants +- `.ai/skills/value-object.md` — Value object (rich class) conventions; encapsulation, boundary unwrapping, composition rules diff --git a/.ai/skills/action.md b/.ai/skills/action.md new file mode 100644 index 00000000..a84f5eae --- /dev/null +++ b/.ai/skills/action.md @@ -0,0 +1,301 @@ +# Action Conventions + +Actions live at `src/actions/` and are the single use-case orchestrators for each command. They validate inputs via Context objects, coordinate Infrastructure services, delegate all UI to their paired Prompts class, and return an `ActionResult`. They never throw to the Command layer and never produce terminal output directly. + +## Conventions + +### DO + +- **DO** use named export: `export class {PascalName}Action`. +- **DO** use `public readonly execute = async (...): Promise =>` (arrow function property). +- **DO** initialize prompts as a field: `private readonly prompts = new {Name}Prompts()`. +- **DO** initialize services as `private readonly` fields (inline with `new`) when the service takes no constructor args. If a service needs `configDir`, declare without initializer and assign in the constructor body after `this.configDir` is set. +- **DO** use the full constructor pattern when the action calls an API: `constructor(configDir: DirectoryPath, commandMetadata: CommandMetadata, authKey: string | null = null)`. +- **DO** use a simpler constructor when no API call is needed: just `(configDir: DirectoryPath, commandMetadata: CommandMetadata)` or even `(configDir: DirectoryPath)`. +- **DO** use constructor shorthand (`private readonly` in parameter list) for minimal actions where no manual field assignment is needed. +- **DO** delegate all terminal output to `this.prompts.*` — never import `log` from `@clack/prompts`. +- **DO** wrap service calls in `this.prompts.spinnerMethod(serviceCall)`. +- **DO** check `result.isErr()` after spinner calls and delegate error display to prompts. +- **DO** use `withDirPath()` to wrap any code that needs a temporary directory. +- **DO** return `ActionResult` directly from the `withDirPath` callback — the return propagates through. +- **DO** use Context objects for input validation (`buildContext.validate()`) and output management (`outputContext.save()`). +- **DO** use the overwrite guard pattern: `if (!force && (await context.exists()) && !(await this.prompts.confirmOverwrite(dir)))`. +- **DO** close file streams in `finally` blocks when using `FileWrapper` or `getStream()`. +- **DO** create sub-action instances inside switch cases (not as class fields) for delegation actions. +- **DO** handle `undefined` (cancel) case explicitly in delegation actions — return `ActionResult.cancelled()`. + +### DON'T + +- **DON'T** use `export default` — actions use named exports. +- **DON'T** use regular `async execute()` method — use the arrow function property form. (Some existing actions use regular methods; new code should use arrow functions.) +- **DON'T** initialize services in the constructor body unless they need a constructor parameter like `configDir`. +- **DON'T** import or call `log.*` from `@clack/prompts` — all output goes through `this.prompts.*`. +- **DON'T** throw exceptions to the Command layer — always return `ActionResult.failed()` or `ActionResult.cancelled()`. +- **DON'T** define helper types/classes inside the action file — put them in `src/types/`. (Exception: small local types used only within the file are acceptable.) +- **DON'T** use `undefined` for optional auth — always use `string | null = null`. +- **DON'T** use `tmp-promise` directly — always use `withDirPath()` from `../../infrastructure/tmp-extensions.js`. +- **DON'T** include `authKey` in the constructor if the action never calls an API — omit it entirely. +- **DON'T** store sub-action instances as class fields — create them per-use in delegation switch cases. +- **DON'T** use `process.exit()` — return an `ActionResult` and let the Command layer handle exit codes via `outro(result)`. +- **DON'T** use `console.log` anywhere. +- **DON'T** use raw string paths — always wrap in `DirectoryPath`, `FilePath`, `FileName`, or `UrlPath`. + +--- + +## Review Checklist + +- [ ] All imports use `.js` extension (e.g., `../../types/file/directoryPath.js`) +- [ ] File placed at `src/actions/{topic}/{name}.ts` mirroring the prompts path +- [ ] Named export (not default): `export class {PascalName}Action` +- [ ] Execute is arrow function: `public readonly execute = async (...): Promise => { ... }` +- [ ] Prompts initialized as field: `private readonly prompts = new {PromptsClassName}()` +- [ ] Services initialized as `private readonly` fields (inline unless they need constructor args) +- [ ] Constructor signature matches complexity: full form for API actions, shorthand for simple ones +- [ ] `authKey` typed as `string | null = null` when present — never `undefined` +- [ ] `authKey` omitted entirely when the action doesn't call an API +- [ ] All terminal output goes through `this.prompts.*` — no direct `log.*` imports +- [ ] Service results checked with `.isErr()`, errors displayed via `this.prompts.*` +- [ ] `withDirPath()` used for temp directories — never raw `tmp-promise` +- [ ] Returns `ActionResult.success()`, `ActionResult.failed()`, or `ActionResult.cancelled()` — never throws +- [ ] Context objects used for validation (`validate()`) and output (`save()`, `exists()`) +- [ ] Overwrite guard uses pattern: `if (!force && (await context.exists()) && !(await this.prompts.confirmOverwrite(dir)))` +- [ ] No `console.log` anywhere +- [ ] No raw string paths — use `DirectoryPath`, `FilePath`, `FileName`, `UrlPath` +- [ ] Relative import depth matches nesting level (extra `../` per nesting level) +- [ ] File streams closed in `finally` block when using `getStream()` or `FileWrapper` +- [ ] Delegation actions handle `undefined` case with `ActionResult.cancelled()` + +## Reference Files + +| Pattern | File | +|---|---| +| Standard action (auth + services + withDirPath) | `src/actions/api/validate.ts` | +| Standard action (multiple params + overwrite guard) | `src/actions/api/transform.ts` | +| Standard action (Context + TempContext + service) | `src/actions/portal/generate.ts` | +| Standard action (SDK generation + version selection) | `src/actions/sdk/generate.ts` | +| Standard action (interactive prompts + neverthrow) | `src/actions/portal/copilot.ts` | +| Minimal action (configDir only, no services) | `src/actions/auth/logout.ts` | +| Minimal action (configDir + commandMetadata, no auth) | `src/actions/auth/status.ts` | +| Minimal action (shorthand constructor) | `src/actions/portal/toc/new-toc.ts` | +| Delegation action (routes to sub-actions) | `src/actions/quickstart.ts` | +| Multi-step flow (withDirPath + multiple cancellation points) | `src/actions/sdk/quickstart.ts` | +| Multi-step flow (interactive wizard + withDirPath) | `src/actions/portal/quickstart.ts` | +| Long-running/stateful action (server + watcher) | `src/actions/portal/serve.ts` | +| ActionResult API (success, failed, cancelled, stopped) | `src/actions/action-result.ts` | +| withDirPath implementation | `src/infrastructure/tmp-extensions.ts` | + +--- + +## Scaffolding + +Use when creating a new Action class. Choose the variant that matches the action's complexity. + +### What to determine + +1. **Topic** — action group folder (e.g., `api`, `sdk`, `portal`). Can be nested: `portal/toc`, `portal/recipe` +2. **Action name** — file name, lowercase hyphenated (e.g., `validate`, `generate`, `new-toc`) +3. **Class name** — PascalCase with `Action` suffix (e.g., `ValidateAction`, `PortalNewTocAction`) +4. **Variant** — one of: + - `standard` — full constructor with configDir, commandMetadata, authKey; services, withDirPath, prompts + - `minimal` — simpler constructor (subset of params); no services or temp dirs + - `delegation` — routes to sub-actions via switch on prompt result +5. **Needs auth** — whether the action receives `authKey: string | null = null` +6. **Needs services** — which infrastructure services are used (e.g., `PortalService`, `ValidationService`) +7. **Needs temp directory** — whether to wrap logic in `withDirPath()` +8. **Needs Context objects** — which contexts are used (e.g., `BuildContext`, `TempContext`, `ResourceContext`) +9. **Prompts class** — the paired Prompts class name and import path +10. **Execute parameters** — typed parameters the execute method receives from the Command layer + +### Standard Action Template + +**Use when:** the action calls API services, needs auth, uses temp directories, or has complex business logic. + +**Path:** `src/actions/{topic}/{name}.ts` + +For nested topics like `portal/toc`, add one more `../` to all relative import paths. + +```typescript +import { DirectoryPath } from "../../types/file/directoryPath.js"; +import { ActionResult } from "../action-result.js"; +import { {PromptsClassName} } from "../../prompts/{topic}/{promptsFile}.js"; +import { CommandMetadata } from "../../types/common/command-metadata.js"; +// If temp directory needed: +import { withDirPath } from "../../infrastructure/tmp-extensions.js"; +// If context objects needed: +// import { ResourceContext } from "../../types/resource-context.js"; +// import { TempContext } from "../../types/temp-context.js"; +// import { BuildContext } from "../../types/build-context.js"; +// If services needed: +// import { {ServiceName} } from "../../infrastructure/services/{service-file}.js"; +// If ResourceInput parameter: +// import { ResourceInput } from "../../types/file/resource-input.js"; + +export class {PascalName}Action { + private readonly prompts = new {PromptsClassName}(); + // Services as private readonly fields (inline initialization when no constructor args needed): + // private readonly someService = new SomeService(); + // + // When service needs configDir, declare without initializer and assign in constructor: + // private readonly validationService: ValidationService; + private readonly configDir: DirectoryPath; + private readonly commandMetadata: CommandMetadata; + private readonly authKey: string | null; + + constructor(configDir: DirectoryPath, commandMetadata: CommandMetadata, authKey: string | null = null) { + this.configDir = configDir; + this.commandMetadata = commandMetadata; + this.authKey = authKey; + // Initialize services that need configDir: + // this.validationService = new ValidationService(configDir); + } + + public readonly execute = async ( + /* parameters matching what Command passes, e.g.: + resourcePath: ResourceInput, + destination: DirectoryPath, + force: boolean + */ + ): Promise => { + // 1. Input validation via Context objects + // const buildContext = new BuildContext(buildDirectory); + // if (!(await buildContext.validate())) { + // this.prompts.invalidBuildDirectory(buildDirectory); + // return ActionResult.failed(); + // } + + // 2. Overwrite confirmation (if force flag) + // if (!force && (await outputContext.exists()) && !(await this.prompts.confirmOverwrite(directory))) { + // this.prompts.destinationNotEmpty(); + // return ActionResult.cancelled(); + // } + + // 3. Business logic wrapped in withDirPath for temp directory + return await withDirPath(async (tempDirectory) => { + // 4. Resolve resources / prepare context + // const resourceContext = new ResourceContext(tempDirectory); + // const specFileDirResult = await resourceContext.resolveTo(resourcePath); + // if (specFileDirResult.isErr()) { + // this.prompts.networkError(specFileDirResult.error); + // return ActionResult.failed(); + // } + + // 5. Service call via prompts spinner + // const response = await this.prompts.spinnerMethod( + // this.someService.doSomething({ + // file: specFileDirResult.value, + // commandMetadata: this.commandMetadata, + // authKey: this.authKey + // }) + // ); + + // 6. Error handling + // if (response.isErr()) { + // this.prompts.serviceError(response.error); + // return ActionResult.failed(); + // } + + // 7. Process success, save output + // this.prompts.outputGenerated(outputDirectory); + + return ActionResult.success(); + }); + }; +} +``` + +**Notes:** +- Constructor assigns `configDir`, `commandMetadata`, and `authKey` explicitly because services or internal methods reference them via `this.*`. +- Services needing `configDir` must be assigned in the constructor body after `this.configDir` is set. +- `withDirPath` returns the `ActionResult` — the entire flow inside the callback must return `ActionResult`. +- Multiple error-check points are normal — each service call or context operation gets its own `.isErr()` check. +- Close file streams in `finally` blocks when working with `FileWrapper` or `getStream()`. + +### Minimal Action Template + +**Use when:** the action has simple logic, no API services, no temp directories, and few constructor dependencies. + +**Path:** `src/actions/{topic}/{name}.ts` + +```typescript +import { DirectoryPath } from "../../types/file/directoryPath.js"; +import { ActionResult } from "../action-result.js"; +import { {PromptsClassName} } from "../../prompts/{topic}/{promptsFile}.js"; +// If commandMetadata needed: +// import { CommandMetadata } from "../../types/common/command-metadata.js"; + +export class {PascalName}Action { + private readonly prompts = new {PromptsClassName}(); + + // Shorthand constructor — fields become private readonly automatically + constructor(private readonly configDir: DirectoryPath) {} + + // With commandMetadata: + // constructor( + // private readonly configDir: DirectoryPath, + // private readonly commandMetadata: CommandMetadata + // ) {} + + public readonly execute = async ( + /* parameters */ + ): Promise => { + // Simple logic — no withDirPath, no services + // Delegate output to this.prompts.* + + return ActionResult.success(); + }; +} +``` + +**Notes:** +- Use TypeScript constructor shorthand when no manual field assignment is needed. +- Omit `authKey` entirely — don't include it if it's never used. +- Omit `commandMetadata` if the action doesn't need it. + +### Delegation Action Template + +**Use when:** the action routes to sub-actions based on user selection (e.g., quickstart flows, wizard-style branching). + +**Path:** `src/actions/{topic}/{name}.ts` + +```typescript +import { DirectoryPath } from "../../types/file/directoryPath.js"; +import { ActionResult } from "../action-result.js"; +import { {PromptsClassName} } from "../../prompts/{topic}/{promptsFile}.js"; +import { CommandMetadata } from "../../types/common/command-metadata.js"; +// Import sub-actions: +// import { SubActionA } from "./{sub-action-a}.js"; +// import { SubActionB } from "./{sub-action-b}.js"; + +export class {PascalName}Action { + private readonly prompts = new {PromptsClassName}(); + + public constructor( + private readonly configDir: DirectoryPath, + private readonly commandMetadata: CommandMetadata + ) {} + + public readonly execute = async (): Promise => { + const selectedFlow = await this.prompts.selectFlow(); + switch (selectedFlow) { + case "optionA": { + const action = new SubActionA(this.configDir, this.commandMetadata); + return await action.execute(); + } + case "optionB": { + const action = new SubActionB(this.configDir, this.commandMetadata); + return await action.execute(); + } + case undefined: { + this.prompts.noFlowSelected(); + return ActionResult.cancelled(); + } + } + }; +} +``` + +**Notes:** +- Create sub-action instances inside the switch case — don't store them as class fields. +- Pass `configDir` and `commandMetadata` through to sub-actions. +- Handle `undefined` (cancel) case explicitly — return `ActionResult.cancelled()`. +- Each case block is wrapped in braces `{ }` for proper scoping of the `action` variable. diff --git a/.ai/skills/command.md b/.ai/skills/command.md new file mode 100644 index 00000000..5119c362 --- /dev/null +++ b/.ai/skills/command.md @@ -0,0 +1,357 @@ +# Command, Action & Prompts Conventions + +These three layers are always created together and mirror each other in path structure. Commands parse flags and orchestrate the flow. Actions contain the business logic. Prompts handle all terminal UI. Each has strict responsibilities — none bleeds into another's domain. + +## Conventions + +### Commands — DO + +- **DO** use `export default class` for all commands — oclif requires default export. +- **DO** use `static readonly` for `summary`, `description`, and `cmdTxt`. +- **DO** use `format.cmd("apimatic", ...topicParts)` for `cmdTxt` — each topic level and the command name are separate arguments. +- **DO** reference the class name (not `this`) in static `examples` array: `${ClassName.cmdTxt}`. +- **DO** use `format.flag("name", "value")` in examples for rendered flag display. +- **DO** place `FlagsProvider.authKey` as the last spread in the flags object. +- **DO** order flags as: custom flags first, then FlagsProvider spreads (`input` → `destination` → `force` → `authKey`). +- **DO** parse flags as the first operation in `run()`, before any other logic. +- **DO** convert raw flag strings to typed values (`DirectoryPath`, `ResourceInput`) immediately after parsing. +- **DO** build `CommandMetadata` in the Command only — never construct it in Action or lower layers. +- **DO** follow the exact `run()` flow: parse → type-convert → CommandMetadata → `intro()` → `action.execute()` → `outro(result)`. +- **DO** use `private readonly getConfigDir = () => new DirectoryPath(this.config.configDir)` when the action needs `configDir`. +- **DO** import only `{ Command }` if no flags, `{ Command, Flags }` if flags exist. +- **DO** add one more `../` to relative imports for each nesting level (e.g., `portal/toc/new.ts` uses `../../../` instead of `../../`). +- **DO** always define both `summary` and `description` static properties. + +### Commands — DON'T + +- **DON'T** use named exports (`export class`) — always use `export default class`. +- **DON'T** use `this` in static context for examples — use `ClassName.cmdTxt` instead. (Some existing files use `this.cmdTxt` in static context; do not follow that pattern in new code.) +- **DON'T** use `private static` for `cmdTxt` — use `static readonly` for consistency. +- **DON'T** define `auth-key` flag inline — use `...FlagsProvider.authKey` spread instead. (The `auth/login.ts` command defines it inline; this is legacy — don't repeat.) +- **DON'T** put business logic in `run()` — only flag parsing, type conversion, and the intro/execute/outro flow. +- **DON'T** omit `summary` — every command must have it. +- **DON'T** call `process.exit()` — use `outro(result)` which sets `process.exitCode`. + +### Actions — DO + +- **DO** use named export: `export class {PascalName}Action`. +- **DO** use `public readonly execute = async (...): Promise =>` (arrow function property). +- **DO** initialize prompts as a field: `private readonly prompts = new {Name}Prompts()`. +- **DO** initialize services as `private readonly` fields (inline with `new`, not in constructor body). +- **DO** use the full constructor pattern when the command calls an API: `constructor(configDir: DirectoryPath, commandMetadata: CommandMetadata, authKey: string | null = null)`. +- **DO** use a simpler constructor when no API call is needed: just `(configDir: DirectoryPath, commandMetadata: CommandMetadata)` or even `(configDir: DirectoryPath)`. +- **DO** delegate all terminal output to `this.prompts.*` — never import `log` from `@clack/prompts`. +- **DO** wrap service calls in `this.prompts.spinnerMethod(serviceCall)`. +- **DO** check `result.isErr()` after spinner calls and delegate error display to prompts. +- **DO** use `withDirPath()` to wrap any code that needs a temporary directory. +- **DO** use Context objects for input validation (`buildContext.validate()`) and output management (`outputContext.save()`). +- **DO** use the overwrite guard pattern: `if (!force && (await context.exists()) && !(await this.prompts.confirmOverwrite(dir)))`. + +### Actions — DON'T + +- **DON'T** use `export default` — actions use named exports. +- **DON'T** use regular `async execute()` method — use the arrow function property form. (Some legacy actions use regular methods; new code should use arrow functions.) +- **DON'T** initialize services in the constructor body that depend on constructor params — initialize services inline on the property declaration, or if they need `configDir`, pass it in the constructor and assign manually. +- **DON'T** import or call `log.*` from `@clack/prompts` — all output goes through `this.prompts.*`. +- **DON'T** throw exceptions to the Command layer — always return `ActionResult.failed()` or `ActionResult.cancelled()`. +- **DON'T** define helper types/classes inside the action file — put them in `src/types/`. +- **DON'T** use `undefined` for optional auth — always use `string | null = null`. + +### Prompts — DO + +- **DO** use named export: `export class {PascalName}Prompts`. +- **DO** import only the `@clack/prompts` functions actually used (e.g., `{ log, confirm, isCancel }`). +- **DO** alias the format import: `import { format as f } from "../format.js"`. +- **DO** use `withSpinner(startMsg, successMsg, failureMsg, promise)` for async operations — it takes `Promise>`. +- **DO** always check `isCancel()` on interactive prompts (`confirm`, `select`, `text`, `multiselect`) and return `false` or `undefined` on cancel. +- **DO** use `confirm({ message: "...", initialValue: false })` for overwrite confirmations. +- **DO** use format helpers for all dynamic content: `f.var("name")` for names, `f.path(dirOrFile)` for paths, `f.link(url)` for URLs. +- **DO** use `noteWrapped(message, title)` from `../prompt.js` for multi-line informational notes (e.g., next steps). +- **DO** use `getTree()` from `../format.js` when displaying directory structures. + +### Prompts — DON'T + +- **DON'T** put business logic in prompts — only UI rendering and user interaction. +- **DON'T** use constructor parameters — prompts classes have no constructor. +- **DON'T** use `console.log` — use `log.*` from `@clack/prompts`. +- **DON'T** forget the `isCancel()` guard — skipping it causes crashes when user presses Ctrl+C. +- **DON'T** concatenate method calls on the same line as string assignment (keep them on separate lines). + +### General — DO + +- **DO** use `.js` extension on all relative imports (even for `.ts` source files). +- **DO** mirror file paths across `commands/`, `actions/`, `prompts/` directories. +- **DO** use typed path objects (`DirectoryPath`, `FilePath`, `FileName`, `UrlPath`) instead of raw strings. +- **DO** use `ActionResult.success(value?)`, `ActionResult.failed(message?)`, or `ActionResult.cancelled()` as return values. +- **DO** use neverthrow `Result` for service returns — check with `.isErr()` / `.isOk()`. + +### General — DON'T + +- **DON'T** use `console.log` anywhere in any layer. +- **DON'T** use raw string paths — always wrap in the appropriate value object. +- **DON'T** use `process.exit()` — use `outro(result)`. +- **DON'T** use colon as topic separator — use space (`apimatic portal generate`, not `apimatic portal:generate`). + +--- + +## Review Checklist + +- [ ] All imports use `.js` extension (e.g., `../../types/file/directoryPath.js`) +- [ ] File paths mirror across `commands/`, `actions/`, `prompts/` directories +- [ ] Command uses `export default class` +- [ ] Command has `static readonly` for summary, description, cmdTxt +- [ ] Command uses `ClassName.cmdTxt` (not `this.cmdTxt`) in examples +- [ ] Command `run()` follows: parse → type-convert → CommandMetadata → intro → execute → outro +- [ ] Command has `getConfigDir()` helper (private readonly arrow function) +- [ ] Action uses named export (not default) +- [ ] Action constructor signature matches expected pattern for its complexity level +- [ ] Action execute is arrow function: `public readonly execute = async (...) => { ... }` +- [ ] Action uses `this.prompts.*` for all output (no direct `log.*` imports) +- [ ] `authKey` typed as `string | null = null`, not `undefined` +- [ ] Prompts spinner methods take `Promise>` and use `withSpinner()` +- [ ] Interactive prompts guard with `isCancel()` returning `false`/`undefined` +- [ ] `FlagsProvider.authKey` is the last spread in `static flags` +- [ ] No `console.log` anywhere +- [ ] Topic separator is space in examples and cmdTxt (not colon) +- [ ] No raw string paths — use `DirectoryPath`, `FilePath`, `FileName`, `UrlPath` +- [ ] Services initialized inline on property declaration (not in constructor body) + +## Reference Files + +| Pattern | File | +|---|---| +| Complex command (multiple flags, FlagsProvider) | `src/commands/sdk/generate.ts` | +| Simple command (ResourceInput, file/url) | `src/commands/api/validate.ts` | +| Minimal command (no flags) | `src/commands/auth/logout.ts` | +| Command with nested topic (3-level) | `src/commands/portal/toc/new.ts` | +| Action with withDirPath + Context objects | `src/actions/portal/generate.ts` | +| Action with Result error handling | `src/actions/api/validate.ts` | +| Action with simpler constructor (no authKey) | `src/actions/portal/toc/new-toc.ts` | +| Action that delegates to sub-actions | `src/actions/quickstart.ts` | +| Prompts with spinner + confirm + select | `src/prompts/sdk/generate.ts` | +| Prompts with error display methods | `src/prompts/portal/generate.ts` | +| Prompts with format helpers | `src/prompts/api/transform.ts` | +| Prompts with noteWrapped + getTree | `src/prompts/quickstart.ts` | + +--- + +## Scaffolding + +Use when creating a new command triple (Command + Action + Prompts). + +### What to determine + +1. **Topic** — command group (e.g., `api`, `sdk`, `portal`). Can be nested: `portal/toc`, `portal/recipe` +2. **Command name** — subcommand (e.g., `validate`, `generate`, `new`) +3. **Summary** — one-line description +4. **Description** — multi-line description (template literal) +5. **Input type** — one of: + - `ResourceInput` — adds `file` + `url` flags, uses `createResourceInput(file, url)` + - `DirectoryPath` — adds `FlagsProvider.input`, uses `DirectoryPath.createInput(input)` + - `None` — no input flags +6. **Needs API auth** — adds `FlagsProvider.authKey`, passes `authKey` to Action constructor +7. **Needs force flag** — adds `FlagsProvider.force` for overwrite confirmation +8. **Needs destination flag** — adds `FlagsProvider.destination(artifact, artifactName)` +9. **Custom flags** — any command-specific flags +10. **Intro text** — text for `intro("...")` call (e.g., "Validate API", "Generate SDK") + +### Command File Template + +**Path:** `src/commands/{topic}/{name}.ts` + +For nested topics like `portal/toc`, add one more `../` to all relative import paths. + +```typescript +import { Command, Flags } from "@oclif/core"; +import { DirectoryPath } from "../../types/file/directoryPath.js"; +import { FlagsProvider } from "../../types/flags-provider.js"; +// If ResourceInput: +// import { createResourceInput } from "../../types/file/resource-input.js"; +import { {PascalName}Action } from "../../actions/{topic}/{name}.js"; +import { CommandMetadata } from "../../types/common/command-metadata.js"; +import { format, intro, outro } from "../../prompts/format.js"; + +export default class {PascalName} extends Command { + static readonly summary = "{summary}"; + + static readonly description = `{description}`; + + // Each topic level is a separate argument to format.cmd + // e.g., format.cmd("apimatic", "portal", "toc", "new") + static readonly cmdTxt = format.cmd("apimatic", "{topic}", "{name}"); + + static examples = [ + `${ClassName.cmdTxt} ${format.flag("flagName", "value")}` + ]; + + static flags = { + // Custom flags first + // Then FlagsProvider spreads in this order: + // ...FlagsProvider.input, + // ...FlagsProvider.destination("{artifact}", "{artifactName}"), + // ...FlagsProvider.force, + // ...FlagsProvider.authKey, <-- always last + }; + + async run() { + const { + flags: { /* destructure all flags, rename "auth-key": authKey */ } + } = await this.parse({PascalName}); + + // Build typed paths from raw flag strings: + // + // For DirectoryPath input: + // const workingDirectory = DirectoryPath.createInput(input); + // const buildDirectory = input ? new DirectoryPath(input, "src") : workingDirectory.join("src"); + // const outputDirectory = destination ? new DirectoryPath(destination) : workingDirectory.join("{artifact}"); + // + // For ResourceInput: + // const resourceInput = createResourceInput(file, url); + + const commandMetadata: CommandMetadata = { + commandName: {PascalName}.id, + shell: this.config.shell + }; + + intro("{Intro Text}"); + const action = new {PascalName}Action(this.getConfigDir(), commandMetadata, authKey); + const result = await action.execute(/* pass typed args */); + outro(result); + } + + private readonly getConfigDir = () => { + return new DirectoryPath(this.config.configDir); + }; +} +``` + +### Action File Template + +**Path:** `src/actions/{topic}/{name}.ts` + +```typescript +import { DirectoryPath } from "../../types/file/directoryPath.js"; +import { ActionResult } from "../action-result.js"; +import { {PascalName}Prompts } from "../../prompts/{topic}/{name}.js"; +import { CommandMetadata } from "../../types/common/command-metadata.js"; +// If temp directory needed: +// import { withDirPath } from "../../infrastructure/tmp-extensions.js"; +// import { TempContext } from "../../types/temp-context.js"; +// If service needed: +// import { SomeService } from "../../infrastructure/services/some-service.js"; +// If context needed: +// import { SomeContext } from "../../types/some-context.js"; + +export class {PascalName}Action { + private readonly prompts: {PascalName}Prompts = new {PascalName}Prompts(); + // Services as private readonly fields: + // private readonly someService: SomeService = new SomeService(); + private readonly configDir: DirectoryPath; + private readonly commandMetadata: CommandMetadata; + private readonly authKey: string | null; + + constructor( + configDir: DirectoryPath, + commandMetadata: CommandMetadata, + authKey: string | null = null + ) { + this.configDir = configDir; + this.commandMetadata = commandMetadata; + this.authKey = authKey; + } + + public readonly execute = async ( + /* parameters matching what Command passes */ + ): Promise => { + // 1. Input validation via Context objects + // const buildContext = new BuildContext(buildDirectory); + // if (!(await buildContext.validate())) { + // this.prompts.directoryEmpty(buildDirectory); + // return ActionResult.failed(); + // } + + // 2. Overwrite confirmation (if force flag) + // const outputContext = new OutputContext(outputDirectory); + // if (!force && (await outputContext.exists()) && !(await this.prompts.confirmOverwrite(outputDirectory))) { + // this.prompts.destinationNotEmpty(); + // return ActionResult.cancelled(); + // } + + // 3. Business logic (wrap in withDirPath if temp dirs needed) + // return await withDirPath(async (tempDirectory) => { + // const tempContext = new TempContext(tempDirectory); + // const zipPath = await tempContext.zip(buildDirectory); + // + // const response = await this.prompts.spinnerMethod( + // this.someService.doSomething(zipPath, this.configDir, this.commandMetadata, this.authKey) + // ); + // + // if (response.isErr()) { + // this.prompts.serviceError(response.error); + // return ActionResult.failed(); + // } + // + // this.prompts.outputGenerated(outputDirectory); + // return ActionResult.success(); + // }); + + return ActionResult.success(); + }; +} +``` + +### Prompts File Template + +**Path:** `src/prompts/{topic}/{name}.ts` + +```typescript +import { log, isCancel, confirm } from "@clack/prompts"; +// Add select, text, multiselect as needed +import { DirectoryPath } from "../../types/file/directoryPath.js"; +import { format as f } from "../format.js"; +import { Result } from "neverthrow"; +import { withSpinner } from "../prompt.js"; +import { ServiceError } from "../../infrastructure/service-error.js"; + +export class {PascalName}Prompts { + // Spinner methods — wrap async service calls + // Takes Promise>, returns the same Result after spinner completes + public async doSomething(fn: Promise>) { + return withSpinner( + "Doing something", // start message + "Done successfully.", // success message + "Failed to do something.", // failure message + fn + ); + } + + // Confirmation methods — return boolean, guard with isCancel + public async confirmOverwrite(directory: DirectoryPath): Promise { + const overwrite = await confirm({ + message: `The destination ${f.path(directory)} is not empty, do you want to overwrite?`, + initialValue: false + }); + if (isCancel(overwrite)) return false; + return overwrite; + } + + // Error display methods + public serviceError(error: ServiceError): void { + log.error(error.errorMessage); + } + + // Info/success display methods + public outputGenerated(outputPath: DirectoryPath): void { + log.info(`Output can be found at ${f.path(outputPath)}.`); + } + + // Validation error messages using format helpers + public directoryEmpty(directory: DirectoryPath): void { + log.error(`The ${f.var("src")} directory is either empty or invalid: ${f.path(directory)}`); + } + + public destinationNotEmpty(): void { + log.error(`Please enter a different destination folder or remove the existing files and try again.`); + } +} +``` diff --git a/.ai/skills/context.md b/.ai/skills/context.md new file mode 100644 index 00000000..9d293ecc --- /dev/null +++ b/.ai/skills/context.md @@ -0,0 +1,244 @@ +# Context Conventions + +Context objects live at `src/types/` and encapsulate path derivation, validation, and file I/O for a single domain concept. Callers interact with high-level behavioral methods (`validate()`, `exists()`, `save()`) — never with internal paths, file names, or parsing logic. There are four variants: output, input, temp, and pure. + +## Conventions + +### Encapsulation — DO + +- **DO** expose only behavioral methods — `validate()`, `exists()`, `save()`, `resolveTo()`. Callers say *what* they want, not *how*. +- **DO** return results from operations — `save()` returning `FilePath` (where it wrote) is fine. It's the *result* of work, not internal *state*. +- **DO** chain method return values between context operations — it's valid for one method's return (`DirectoryPath`, `FilePath`) to be passed as input to the next method on the same context. This is coordinated workflow within the context's domain, not leakage. +- **DO** keep derived paths as `private get` — all `FilePath`/`DirectoryPath` derivation is internal. +- **DO** keep decision logic inside — if the context knows how to choose between zip/unzip, file/url, etc., that logic stays internal. +- **DO** expose domain-specific read/write methods — instead of returning raw JSON/YAML config for callers to manipulate. +- **DO** initialize `FileService` / `ZipService` inline as `private readonly` fields. +- **DO** use constructor shorthand (`private readonly` in parameter list) for all constructor parameters. +- **DO** use `new FilePath(directory, new FileName("name"))` for file path construction. +- **DO** use `directory.join("subdir")` for subdirectory derivation. + +### Encapsulation — DON'T + +- **DON'T** expose internal paths as public getters — no `public get outputDirectory()` or `public get filePath()`. If a caller needs a path, it should come as a return value from an operation. +- **DON'T** return raw config objects — don't return parsed JSON/YAML for callers to manipulate directly. Wrap reads/writes in domain methods (e.g., `getCopilotConfig()` instead of `getBuildFileContents()`). +- **DON'T** expose derived file names — methods like `getScriptFileName()` leak internal naming logic. +- **DON'T** add public properties for internal state — constructor parameters are `private readonly`, not exposed. +- **DON'T** use `console.log` or any prompt output — contexts are silent. +- **DON'T** use `Result` unless the context does network I/O (rare). +- **DON'T** use raw string paths — always wrap in `DirectoryPath`, `FilePath`, `FileName`. +- **DON'T** add methods that only use infrastructure services (`fileService`, `zipService`) without touching domain-specific private fields — these are stateless utilities, not context behavior. Every public method must use at least one constructor-derived private field (e.g., `sdkDirectory`, `language`). If a method takes all its inputs as parameters and never reads context state, it belongs in a service or a different context. +- **DON'T** embed the context's own domain subject in method names — if the class is `SdkContext`, a method named `cleanUpSdkDirectory()` is redundant. Prefer concise behavioral verbs: `cleanUp()`, `getChanges()`, `save()`. +- **DON'T** pass internal paths through callback parameters — `onSomething: (path: DirectoryPath) => void` is the same leakage as a public getter. If the caller needs the path, return it as the result of the operation (`Promise` etc.). + +### Variant-specific rules + +**Output contexts** (`exists()` + `save()` pattern): +- `exists()` checks if output is already populated — used by actions for overwrite confirmation. +- `save()` returns the path where content was written — only way callers learn about output location. +- Call `cleanDirectory` before writing when overwriting is expected. + +**Input contexts** (`validate()` + domain read/write methods): +- `validate()` checks that required files/directories exist before any operation. +- Read/write methods expose domain concepts, not raw file contents. +- Callers should never need to know file names, formats, or internal structure. + +**Temp contexts** (used inside `withDirPath()` blocks): +- Methods create temp files and return the resulting `FilePath`. +- Temp file naming (UUIDs, etc.) is internal — never exposed. + +**Pure contexts** (no I/O): +- No `FileService`, `ZipService`, or any infrastructure imports. +- Constructor takes domain values (strings, enums, DTOs). +- Methods are pure transformations or in-memory checks. + +--- + +## Review Checklist + +- [ ] All imports use `.js` extension (e.g., `../infrastructure/file-service.js`) +- [ ] File placed at `src/types/{name}-context.ts` +- [ ] Named export (not default): `export class {PascalName}Context` +- [ ] `private readonly fileService = new FileService()` — inline, not constructor-injected +- [ ] Constructor params as `private readonly` (shorthand or explicit) +- [ ] **All derived paths are `private get`** — no public getters for internal paths +- [ ] **No raw config objects returned** — domain methods instead of `getContents()` +- [ ] Operations (`save`, `validate`) return results; internal state is not exposed +- [ ] Paths use value objects: `DirectoryPath`, `FilePath`, `FileName` +- [ ] `new FilePath(directory, new FileName("name"))` for file path construction +- [ ] `directory.join("subdir")` for subdirectory derivation +- [ ] No `console.log`, no prompt output — contexts are silent +- [ ] No `Result` unless context does network I/O (rare) +- [ ] Every public method uses at least one domain-specific private field — methods using only infrastructure services don't belong here +- [ ] No public properties that expose constructor parameters +- [ ] Method names do not embed the context's own domain subject — names are concise behavioral verbs (`cleanUp()` not `cleanUpSdkReviewDirectory()`) +- [ ] No internal paths passed through callback parameters — paths are returned as operation results, not injected into callbacks + +## Reference Files + +| Pattern | File | Encapsulation | +|---|---|---| +| Output context (save + zip/unarchive) | `src/types/portal-context.ts` | Good — all paths private | +| Output context (save stream + derived name) | `src/types/transform-context.ts` | Good — name derivation internal | +| Input context (validate + file ops) | `src/types/spec-context.ts` | Good — zip detection internal | +| Temp context (zip + save stream) | `src/types/temp-context.ts` | Good — UUID naming internal | +| Temp context (download + resolve) | `src/types/resource-context.ts` | Good — URL/file decision internal | +| Composite (delegates to BuildContext) | `src/types/versioned-build-context.ts` | Good — typed result object | +| Output context (leaky — avoid pattern) | `src/types/sdk-context.ts` | Avoid — exposes `sdkLanguageDirectory`, has methods that only use infrastructure services without touching domain state | +| Input context (leaky — avoid pattern) | `src/types/toc-context.ts` | Avoid — exposes `tocPath` | + +--- + +## Scaffolding + +Use when creating a new Context class. Choose the variant that matches the domain concept. + +### What to determine + +1. **Context name** — lowercase hyphenated (e.g., `portal`, `sdk`, `build`). File will be `{name}-context.ts` +2. **Class name** — PascalCase (e.g., `PortalContext`, `SdkContext`) +3. **Variant** — one of: + - `output` — save/exists pattern for command outputs (portals, SDKs, transformed files) + - `input` — validate/read pattern for user-provided input directories + - `temp` — transient operations inside `withDirPath()` blocks + - `pure` — no I/O, domain logic only (name derivation, in-memory checks) +4. **Constructor parameters** — what domain values the context wraps (directories, file paths, enums) +5. **Services needed** — `FileService`, `ZipService`, `FileDownloadService` (none for pure) +6. **Initial methods** — what operations the context should expose + +### Output Context Template + +**Use when:** the context manages command output (portals, SDKs, transformed files). + +**Based on:** `src/types/portal-context.ts`, `src/types/transform-context.ts` + +```typescript +import { FileService } from "../infrastructure/file-service.js"; +import { DirectoryPath } from "./file/directoryPath.js"; +import { FilePath } from "./file/filePath.js"; +import { FileName } from "./file/fileName.js"; +// If zip/unarchive needed: +// import { ZipService } from "../infrastructure/zip-service.js"; + +export class {PascalName}Context { + private readonly fileService = new FileService(); + // private readonly zipService = new ZipService(); + + constructor(private readonly outputDirectory: DirectoryPath) {} + + // Internal path derivation — always private + private get outputPath(): FilePath { + return new FilePath(this.outputDirectory, new FileName("{filename}")); + } + + public async exists(): Promise { + return !(await this.fileService.directoryEmpty(this.outputDirectory)); + } + + // save() returns the output path as a result of the operation + public async save(stream: NodeJS.ReadableStream): Promise { + await this.fileService.createDirectoryIfNotExists(this.outputDirectory); + await this.fileService.writeFile(this.outputPath, stream); + return this.outputPath; + } + + // For zip/unarchive branching: + // public async save(tempFilePath: FilePath, asZip: boolean): Promise { + // await this.fileService.cleanDirectory(this.outputDirectory); + // if (asZip) { + // await this.fileService.copy(tempFilePath, this.zipPath); + // } else { + // await this.zipService.unArchive(tempFilePath, this.outputDirectory); + // } + // } +} +``` + +### Input Context Template + +**Use when:** the context validates and reads from user-provided input directories. + +**Based on:** `src/types/spec-context.ts` + +```typescript +import { FileService } from "../infrastructure/file-service.js"; +import { DirectoryPath } from "./file/directoryPath.js"; +import { FilePath } from "./file/filePath.js"; +import { FileName } from "./file/fileName.js"; + +export class {PascalName}Context { + private readonly fileService = new FileService(); + + constructor(private readonly inputDirectory: DirectoryPath) {} + + // Internal path derivation — always private + private get configFile(): FilePath { + return new FilePath(this.inputDirectory, new FileName("{config-file-name}")); + } + + public async validate(): Promise { + if (!(await this.fileService.directoryExists(this.inputDirectory))) return false; + return await this.fileService.fileExists(this.configFile); + } + + // Domain-specific read methods — NOT raw config getters + // public async getSomeDomainValue(): Promise { + // const content = await this.fileService.getContents(this.configFile); + // const config = JSON.parse(content); + // return config.domainField; + // } +} +``` + +### Temp Context Template + +**Use when:** the context manages transient operations inside `withDirPath()` blocks. + +**Based on:** `src/types/temp-context.ts`, `src/types/resource-context.ts` + +```typescript +import { FileService } from "../infrastructure/file-service.js"; +import { DirectoryPath } from "./file/directoryPath.js"; +import { FilePath } from "./file/filePath.js"; +import { FileName } from "./file/fileName.js"; +// If zip needed: +// import { ZipService } from "../infrastructure/zip-service.js"; +// import { randomUUID } from "crypto"; + +export class {PascalName}Context { + private readonly fileService = new FileService(); + + constructor(private readonly tempDirectory: DirectoryPath) {} + + // Operations return the resulting FilePath + public async save(stream: NodeJS.ReadableStream): Promise { + const tempFile = new FilePath(this.tempDirectory, new FileName("{temp-name}")); + await this.fileService.writeFile(tempFile, stream); + return tempFile; + } + + // public async zip(sourceDirectory: DirectoryPath): Promise { + // const tempFile = new FilePath(this.tempDirectory, new FileName(randomUUID())); + // await this.zipService.archive(sourceDirectory, tempFile); + // return tempFile; + // } +} +``` + +### Pure Context Template + +**Use when:** the context has domain logic but no file/network I/O. + +**Based on:** `src/types/recipe-context.ts` + +```typescript +// No infrastructure imports — pure logic only + +export class {PascalName}Context { + constructor(private readonly {domainParam}: {Type}) {} + + // Domain logic methods — no I/O + public {methodName}({params}): {ReturnType} { + // Pure transformation or validation logic + } +} +``` diff --git a/.ai/skills/event.md b/.ai/skills/event.md new file mode 100644 index 00000000..7ff561fa --- /dev/null +++ b/.ai/skills/event.md @@ -0,0 +1,110 @@ +# Domain Event Conventions + +Domain events live at `src/types/events/` and represent something that **already happened** in the system. They are fired from the Command layer via `TelemetryService.trackEvent()` and extend `DomainEvent`. + +## Conventions + +### Naming + +- **Class name must be past tense** — the event describes something that occurred: `QuickstartCompleted`, `SdkChangesSaved`, `RecipeCreationFailed`. Never present tense (`SdkSaveChanges`) or imperative (`SaveChanges`). +- **File name** — lowercase hyphenated, matching the class name: `quickstart-completed.ts`, `sdk-changes-saved.ts`. +- **Class name suffix** — always `Event`: `QuickstartCompletedEvent`, `SdkChangesSavedEvent`. + +### Structure + +Two variants exist depending on whether the event carries runtime data (e.g., `language`) or is parameterised entirely by its own static fields. + +#### Self-contained event (no runtime data beyond what the event already knows) + +```typescript +import { DomainEvent } from "./domain-event.js"; + +export class {PascalName}Event extends DomainEvent { + protected readonly eventName = {PascalName}Event.name; + private static readonly message = "{Human readable past-tense description}." as const; + private static readonly commandName = "{topic}:{command}" as const; + + constructor() { + super({PascalName}Event.message, {PascalName}Event.commandName, {}); + } +} +``` + +#### Event with runtime payload (e.g., language, flags) + +```typescript +import { DomainEvent } from "./domain-event.js"; + +export class {PascalName}Event extends DomainEvent { + protected readonly eventName = {PascalName}Event.name; + private static readonly message = "{Human readable past-tense description}." as const; + private static readonly commandName = "{topic}:{command}" as const; + private readonly {payloadField}: {PayloadType}; + + constructor({payloadField}: {PayloadType}) { + super({PascalName}Event.message, {PascalName}Event.commandName, {}); + this.{payloadField} = {payloadField}; + } +} +``` + +#### Failure event (message, commandName, and flags passed in from the Command layer) + +Use this variant when the event needs to capture dynamic failure context (e.g., the failing command's flags): + +```typescript +import { DomainEvent } from "./domain-event.js"; + +export class {PascalName}Event extends DomainEvent { + protected readonly eventName = {PascalName}Event.name; + + constructor(message: string, commandName: string, flags: Record) { + super(message, commandName, flags); + } +} +``` + +### DO + +- **DO** use past-tense names: `Completed`, `Initiated`, `Failed`, `Resolved`, `Saved`, `Tracked`. +- **DO** mark `message` and `commandName` as `private static readonly ... as const`. +- **DO** assign `eventName` as `protected readonly eventName = {ClassName}.name` — never a raw string. +- **DO** use named export: `export class {PascalName}Event`. +- **DO** place the file at `src/types/events/{kebab-name}.ts`. +- **DO** use `.js` extension in the import: `import { DomainEvent } from "./domain-event.js"`. +- **DO** fire events from the Command layer only — never from Actions or Services. +- **DO** fire success events inside the `mapAll` success callback (first arg); failure events in the failure callback (second arg). + +### DON'T + +- **DON'T** use present tense or imperative class names (`SdkSaveChanges`, `TrackChanges`). +- **DON'T** use `export default`. +- **DON'T** fire events from Actions, Prompts, or Infrastructure — only from Commands. +- **DON'T** add business logic inside an event class — it is a plain data carrier. + +--- + +## Review Checklist + +- [ ] Class name is past tense and ends with `Event` +- [ ] File name is kebab-case and matches the class name +- [ ] `eventName` assigned as `ClassName.name` (not a raw string) +- [ ] `message` and `commandName` are `private static readonly ... as const` (self-contained variant) +- [ ] Named export (not default) +- [ ] Import uses `.js` extension: `"./domain-event.js"` +- [ ] Fired from Command layer only, inside `result.mapAll(...)` callback + +--- + +## Reference Files + +| Pattern | File | +|---|---| +| Self-contained success event (no payload) | `src/types/events/quickstart-completed.ts` | +| Self-contained initiation event (no payload) | `src/types/events/quickstart-initiated.ts` | +| Success event with payload (`language`) | `src/types/events/sdk-changes-saved.ts` | +| Success event with payload (`language`) | `src/types/events/sdk-conflicts-resolved.ts` | +| Failure event (dynamic message + flags) | `src/types/events/recipe-creation-failed.ts` | +| DomainEvent base class | `src/types/events/domain-event.ts` | +| Firing from Command (success + failure) | `src/commands/quickstart.ts` | +| Firing from Command (failure only) | `src/commands/portal/recipe/new.ts` | diff --git a/.ai/skills/prompt.md b/.ai/skills/prompt.md new file mode 100644 index 00000000..50cd1ebc --- /dev/null +++ b/.ai/skills/prompt.md @@ -0,0 +1,312 @@ +# Prompt Conventions + +Prompts live at `src/prompts/` and are the sole terminal UI layer for each command. They are thin wrappers around `@clack/prompts` — no constructor, no business logic, no knowledge of domain rules. Four method categories: spinners, interactive prompts, log messages, and note/tree display. Everything else belongs in the Action layer. + +## Conventions + +### DO + +- **DO** use named export: `export class {PascalName}Prompts`. +- **DO** omit the constructor — prompts classes are always stateless with no fields. +- **DO** import only the `@clack/prompts` functions actually used (e.g., `{ log, confirm, isCancel }`). +- **DO** alias the format import: `import { format as f } from "../format.js"` (add one more `../` per nesting level). +- **DO** use `withSpinner(intro, success, failure, fn)` from `../prompt.js` for all async `Result` operations. +- **DO** always check `isCancel()` on interactive prompts (`confirm`, `select`, `text`, `multiselect`) before using the value. +- **DO** return `false` from `confirm` methods on cancel — not `undefined`. +- **DO** return `undefined` from `select`, `text`, and `multiselect` methods on cancel. +- **DO** use `confirm({ message: "...", initialValue: false })` for overwrite/destructive confirmations. +- **DO** use format helpers for all dynamic content: `f.var("name")` for variables, `f.path(dir)` for paths, `f.link(url)` for URLs. +- **DO** use `log.error()` for errors, `log.info()` for success/info, `log.warning()` for warnings, `log.message()` for multi-line output, `log.step()` for step markers. +- **DO** use `noteWrapped(message, title)` from `../prompt.js` for multi-line notes (e.g., next steps). +- **DO** use `getTree()` from `../format.js` when displaying directory structures. +- **DO** name spinner methods after their operation, matching the paired service call (e.g., `generatePortal(fn)`, `validateApi(fn)`). + +### DON'T + +- **DON'T** use a constructor — no parameters, no fields. +- **DON'T** put business logic in Prompts — only UI rendering and user interaction. This is a wrapper around `@clack/prompts`, nothing more. +- **DON'T** use `console.log` — use `log.*` from `@clack/prompts`. +- **DON'T** skip the `isCancel()` guard on interactive prompts — omitting it causes a crash when the user presses Ctrl+C. +- **DON'T** return the raw `symbol` value from interactive prompts — always guard with `isCancel()` first. +- **DON'T** import `ActionResult`, services, or domain logic — those belong in the Action layer. +- **DON'T** use `export default` — always use named exports. +- **DON'T** use raw string paths — wrap in `DirectoryPath`, `FilePath`, or `UrlPath`. +- **DON'T** chain complex format calls on a single line — build the message string on a separate line first. If the string is too long to fit on one line, split it using `+` across multiple template literals: + ```typescript + const message = + `First part ${f.var(name)}. ` + + `Second part ${f.flag("flag-name")}.`; + ``` +- **DON'T** use `format.*` directly — always alias as `f`: `import { format as f }`. + +--- + +## Review Checklist + +- [ ] All imports use `.js` extension (e.g., `../../types/file/directoryPath.js`) +- [ ] File placed at `src/prompts/{topic}/{name}.ts` mirroring the action/command path +- [ ] Named export (not default): `export class {PascalName}Prompts` +- [ ] No constructor — class body starts directly with methods +- [ ] `@clack/prompts` imports are selective (only functions actually used) +- [ ] `format` aliased as `f`: `import { format as f } from "../format.js"` +- [ ] Import depth matches nesting level (one extra `../` per nesting level) +- [ ] All interactive prompts check `isCancel()` before using the result +- [ ] `confirm` returns `false` on cancel; `select`/`text`/`multiselect` return `undefined` on cancel +- [ ] All dynamic content uses `f.var()`, `f.path()`, `f.link()`, etc. +- [ ] No `console.log`, no business logic, no service or ActionResult imports +- [ ] Spinner methods accept `Promise>` and delegate to `withSpinner()` + +--- + +## Reference Files + +| Pattern | File | +|---|---| +| Simple (log + spinner, no interactive prompts) | `src/prompts/auth/login.ts` | +| Simple (log-only display methods) | `src/prompts/auth/status.ts` | +| Standard (confirm + log + spinner) | `src/prompts/portal/generate.ts` | +| Standard (confirm + select + log + spinner) | `src/prompts/sdk/generate.ts` | +| Standard (log + spinner + structured display) | `src/prompts/api/validate.ts` | +| Delegation (select flow + welcome message) | `src/prompts/quickstart.ts` | +| Wizard (multi-step, noteWrapped, getTree) | `src/prompts/portal/quickstart.ts` | +| Shared utilities (withSpinner, noteWrapped) | `src/prompts/prompt.ts` | +| Format helpers (f.var, f.path, f.link, getTree) | `src/prompts/format.ts` | + +--- + +## Scaffolding + +Use when creating a new Prompts class. Choose the variant that matches the command's UI complexity. + +### What to determine + +1. **Topic** — folder path matching the action (e.g., `auth`, `api`, `sdk`, `portal`, `portal/toc`) +2. **Name** — file name, lowercase hyphenated (e.g., `login`, `generate`, `new-toc`) +3. **Class name** — PascalCase with `Prompts` suffix (e.g., `LoginPrompts`, `SdkGeneratePrompts`) +4. **Variant** — one of: + - `simple` — log methods and optional spinner; no interactive prompts + - `standard` — overwrite confirm + error log methods + spinner + - `delegation` — welcome message + async select returning a typed flow union + - `wizard` — multi-step with step markers, multiple prompt types, noteWrapped, getTree +5. **Interactive prompts needed** — which of: `confirm`, `select`, `text`, `multiselect` +6. **Spinner needed** — yes/no, and what `Result` type it wraps +7. **Format helpers needed** — which of: `f.var`, `f.path`, `f.link`, `noteWrapped`, `getTree` + +### Simple Template + +**Use when:** the command only needs to display messages and wrap a service call in a spinner — no interactive prompts. + +**Based on:** `src/prompts/auth/login.ts` + +**Path:** `src/prompts/{topic}/{name}.ts` + +```typescript +import { log } from "@clack/prompts"; +import { Result } from "neverthrow"; +import { ServiceError } from "../../infrastructure/service-error.js"; +import { withSpinner } from "../prompt.js"; +// Add if messages use dynamic content: +// import { format as f } from "../format.js"; +// Add typed path imports as needed: +// import { DirectoryPath } from "../../types/file/directoryPath.js"; + +export class {PascalName}Prompts { + public {action}(fn: Promise>) { + return withSpinner("{Action started}", "{Action} completed successfully.", "{Action} failed.", fn); + } + + public {errorMethod}() { + log.error("{Error message}"); + } + + public {successMethod}() { + log.info("{Success message}"); + } +} +``` + +**Notes:** +- Omit `withSpinner` import entirely if the command has no async service call. +- Add `import { format as f } from "../format.js"` when any message includes a dynamic value. +- Adjust `../` depth: one more level per nesting (e.g., `portal/toc/` uses `../../`). + +### Standard Template + +**Use when:** the command confirms before overwriting, calls a service, and displays error/success messages. + +**Based on:** `src/prompts/portal/generate.ts`, `src/prompts/sdk/generate.ts` + +**Path:** `src/prompts/{topic}/{name}.ts` + +```typescript +import { isCancel, confirm, log } from "@clack/prompts"; +import { Result } from "neverthrow"; +import { DirectoryPath } from "../../types/file/directoryPath.js"; +import { ServiceError } from "../../infrastructure/service-error.js"; +import { format as f } from "../format.js"; +import { withSpinner } from "../prompt.js"; + +export class {PascalName}Prompts { + public async confirmOverwrite(directory: DirectoryPath): Promise { + const overwrite = await confirm({ + message: `The destination ${f.path(directory)} is not empty, do you want to overwrite?`, + initialValue: false + }); + + if (isCancel(overwrite)) { + return false; + } + + return overwrite; + } + + public {action}(fn: Promise>) { + return withSpinner("{Action started}", "{Action} completed.", "{Action} failed.", fn); + } + + public {domainError}() { + log.error("{Domain-specific error message}"); + } + + public serviceError(serviceError: ServiceError) { + log.error(serviceError.errorMessage); + } + + public {successMethod}({param}: {Type}) { + log.info(`{message}`); + } +} +``` + +**Notes:** +- `confirm` returns `false` on cancel — never `undefined`. +- Add `select` or `text` as additional interactive methods with the same `isCancel()` guard + `return undefined` pattern. +### Delegation Template + +**Use when:** the command is a top-level router that lets the user choose a sub-flow. + +**Based on:** `src/prompts/quickstart.ts` + +**Path:** `src/prompts/{topic}/{name}.ts` + +```typescript +import { isCancel, log, select } from "@clack/prompts"; + +export type {Feature}Flow = "optionA" | "optionB" | undefined; + +export class {PascalName}Prompts { + public welcomeMessage() { + log.info("{Welcome message}"); + log.message("{Brief description of what this wizard does.}"); + } + + public async select{Feature}Flow(): Promise<{Feature}Flow> { + const option = await select({ + message: "{What would you like to do?}", + options: [ + { value: "optionA", label: "{Label A}", hint: "{Optional hint}" }, + { value: "optionB", label: "{Label B}" } + ] + }); + + if (isCancel(option)) { + return undefined; + } + + return option; + } + + public noFlowSelected() { + log.error("No option was selected."); + } +} +``` + +**Notes:** +- Export the flow union type from the same file — the paired Action imports it for type-safe `switch` cases. +- `undefined` in the union represents cancellation — the Action returns `ActionResult.cancelled()` for that case. +- Add `import { format as f } from "../format.js"` only if messages include dynamic content. + +### Wizard Template + +**Use when:** the command is a multi-step interactive flow with step markers, multiple prompt types, and rich output formatting. + +**Based on:** `src/prompts/portal/quickstart.ts` + +**Path:** `src/prompts/{topic}/{name}.ts` + +```typescript +import { isCancel, log, confirm, select, text, multiselect } from "@clack/prompts"; +import { Result } from "neverthrow"; +import { DirectoryPath } from "../../types/file/directoryPath.js"; +import { ServiceError } from "../../infrastructure/service-error.js"; +import { format as f } from "../format.js"; +import { withSpinner, noteWrapped } from "../prompt.js"; +import { getTree } from "../format.js"; +// Additional domain type imports as needed + +export class {PascalName}Prompts { + // Step markers — sync, no return value + public {step}Step() { + log.step("Step {N}: {Step title}"); + } + + // Text input prompt + public async {input}Prompt(): Promise { + const value = await text({ + message: "{Prompt message}", + placeholder: "{placeholder}", + validate: (v) => (!v ? "{Field} is required." : undefined) + }); + + if (isCancel(value)) { + return undefined; + } + + return value; + } + + // Multi-select prompt + public async select{Items}Prompt(options: string[]): Promise { + const selected = await multiselect({ + message: "{Select message}", + options: options.map((o) => ({ value: o, label: o })), + required: true + }); + + if (isCancel(selected)) { + return undefined; + } + + return selected; + } + + // Spinner for async Result operations + public {action}(fn: Promise>) { + return withSpinner("{Action started}", "{Action} completed.", "{Action} failed.", fn); + } + + // Multi-line note + public nextSteps() { + const message = ["{Step 1}", "{Step 2}"].join("\n"); + noteWrapped(message, "Next steps"); + } + + // Directory tree display + public showStructure(directory: DirectoryPath) { + log.message(getTree(directory)); + } + + public {errorMethod}() { + log.error("{Error message}"); + } +} +``` + +**Notes:** +- Wizard prompts return `undefined` on cancel — the Action checks for `undefined` and returns `ActionResult.cancelled()`. +- `text()` `validate` callback returns a string (error message) or `undefined` (valid) — this is clack UI validation only, not business logic. +- Use `multiselect` with `required: true` to prevent empty selection without extra logic in the Action. +- Step markers are sync void methods called by the Action before the matching interactive prompt. +- `noteWrapped` handles terminal-width overflow automatically — prefer it over `note()` for long messages. +- Remove unused prompt imports (e.g., omit `multiselect` if the wizard has no multi-select step). diff --git a/.ai/skills/service.md b/.ai/skills/service.md new file mode 100644 index 00000000..5ef29073 --- /dev/null +++ b/.ai/skills/service.md @@ -0,0 +1,258 @@ +# Service Conventions + +Services live at `src/infrastructure/services/` and are the only layer that makes external API calls. Every public method returns `Promise>` using neverthrow — services never throw to their callers. There are three variants: SDK controller (wraps `@apimatic/sdk`), axios with auth, and stateless axios. + +## Conventions + +### DO + +- **DO** use named export: `export class {PascalName}Service`. +- **DO** return `Promise>` from all public methods — use `ok()` and `err()` from neverthrow. +- **DO** catch `ProblemDetailsError` first (for 400/403 with structured error messages), then fall back to `handleServiceError(error)`. +- **DO** close file streams in `finally` blocks when using `FileWrapper` or `getStream()`. +- **DO** resolve auth as: `getAuthInfo(configDir.toString())` → `createAuthorizationHeader()` → `apiClientFactory.createApiClient()`. +- **DO** use `createAuthorizationHeader` as a private arrow function. +- **DO** use `envInfo.getBaseUrl()` as a fallback base URL — allows overriding in test environments. +- **DO** use `as const` on base URL string literals. +- **DO** initialize `FileService` inline as a `private readonly` field. +- **DO** format auth header as `X-Auth-Key ${key ?? ""}` (space, not colon). +- **DO** pre-check auth before any API call in axios-auth services: `if (authInfo === null && !authKey) return err(ServiceError.UnAuthorized)`. +- **DO** use `authKey || authInfo?.authKey` for token resolution — flag override takes precedence. +- **DO** define response types as `interface` or `type` (in the file or in `src/types/`). + +### DON'T + +- **DON'T** throw — always return `err(...)` wrapped in `ServiceError`. +- **DON'T** use `console.log` — services are silent; errors communicated via `Result`. +- **DON'T** use raw string paths — use `DirectoryPath`, `FilePath`, `UrlPath`, `FileName`. +- **DON'T** use `axios.create()` as a class field — create a fresh instance per call in `axiosInstance()` method. +- **DON'T** use `ApiError` as the primary catch branch — `ProblemDetailsError` should be caught first. +- **DON'T** skip the `finally` block when a `FileWrapper` or stream is opened. +- **DON'T** hardcode base URLs without falling back to `envInfo.getBaseUrl()` (except for third-party endpoints). + +### SDK controller variant rules + +- Instantiate controller per method call: `new {ControllerName}(client)` inside the method. +- `apiClientFactory.createApiClient(authHeader, shell)` provides the configured client. +- For async/polling SDK methods, poll until status is terminal — see `portal-service.ts` for the pattern. + +### Axios-auth variant rules + +- `axiosInstance(shell, token)` is a private method (not arrow function) that returns a fresh axios instance. +- Use `envInfo.getAuthBaseUrl()` for auth-specific endpoints (separate from the main API base URL). +- Add `validateStatus: () => true` to handle non-2xx responses without throwing. + +### Stateless variant rules + +- No `axiosInstance` factory — use `axios.get()`/`axios.post()` directly. +- No auth imports or resolution. +- URL is passed as a parameter or hardcoded per method. + +--- + +## Review Checklist + +- [ ] All imports use `.js` extension (e.g., `../../client-utils/auth-manager.js`) +- [ ] File placed at `src/infrastructure/services/{name}-service.ts` +- [ ] Named export (not default): `export class {PascalName}Service` +- [ ] All public methods return `Promise>` using neverthrow +- [ ] Uses `ok()` and `err()` from neverthrow — never throws +- [ ] Catch blocks use `handleServiceError(error)` as fallback +- [ ] Auth uses `getAuthInfo(configDir.toString())` — note `.toString()` on DirectoryPath +- [ ] Auth header format: `X-Auth-Key ${key ?? ""}` (space, not colon) +- [ ] `private readonly` for all field declarations +- [ ] `as const` on base URL string literals +- [ ] No `console.log` — services are silent +- [ ] No raw string paths — use `DirectoryPath`, `FilePath`, `UrlPath`, `FileName` +- [ ] Response types defined as `interface` or `type` +- [ ] File streams closed in `finally` block when using `FileWrapper` + +## Reference Files + +| Pattern | File | +|---|---| +| SDK controller + async polling | `src/infrastructure/services/portal-service.ts` | +| SDK controller + FormData | `src/infrastructure/services/validation-service.ts` | +| Raw axios with auth + axiosInstance | `src/infrastructure/services/api-service.ts` | +| Raw axios with different base URL | `src/infrastructure/services/auth-service.ts` | +| Stateless axios (no auth) | `src/infrastructure/services/file-download-service.ts` | +| ServiceError class + handleServiceError | `src/infrastructure/service-error.ts` | +| SDK client factory (singleton) | `src/infrastructure/services/api-client-factory.ts` | +| envInfo (base URLs, user agent) | `src/infrastructure/env-info.ts` | + +--- + +## Scaffolding + +Use when creating a new infrastructure service. Choose the variant that matches the API access pattern. + +### What to determine + +1. **Service name** — lowercase hyphenated (e.g., `copilot`, `billing`). File will be `{name}-service.ts` +2. **Class name** — PascalCase (e.g., `CopilotService`, `BillingService`) +3. **Variant** — one of: + - `sdk-controller` — uses `@apimatic/sdk` controller classes via `apiClientFactory` + - `axios-auth` — raw axios with auth (base URL, `axiosInstance` factory, auth pre-check) + - `axios-stateless` — raw axios without auth (direct calls) +4. **Needs auth** — whether methods resolve auth via `getAuthInfo` + `authKey` parameter +5. **Initial method** — name, parameters, and return type for the first method to scaffold +6. **Response type** — type/interface for the success value + +### SDK Controller Service Template + +**Use when:** the service wraps an `@apimatic/sdk` controller (e.g., generation, transformation, validation). + +**Based on:** `src/infrastructure/services/portal-service.ts`, `src/infrastructure/services/validation-service.ts` + +```typescript +import { + {ControllerName}, + ContentType, + FileWrapper, + ProblemDetailsError, + ApiError, +} from "@apimatic/sdk"; +import { AuthInfo, getAuthInfo } from "../../client-utils/auth-manager.js"; +import { DirectoryPath } from "../../types/file/directoryPath.js"; +import { FilePath } from "../../types/file/filePath.js"; +import { FileService } from "../file-service.js"; +import { apiClientFactory } from "./api-client-factory.js"; +import { CommandMetadata } from "../../types/common/command-metadata.js"; +import { err, ok, Result } from "neverthrow"; +import { handleServiceError, ServiceError } from "../service-error.js"; + +export class {PascalName}Service { + private readonly CONTENT_TYPE = ContentType.EnumMultipartformdata; + private readonly fileService = new FileService(); + + public async {methodName}( + filePath: FilePath, + configDir: DirectoryPath, + commandMetadata: CommandMetadata, + authKey: string | null + ): Promise> { + const fileStream = await this.fileService.getStream(filePath); + const file = new FileWrapper(fileStream); + + const authInfo: AuthInfo | null = await getAuthInfo(configDir.toString()); + const authorizationHeader = this.createAuthorizationHeader(authInfo, authKey); + const client = apiClientFactory.createApiClient(authorizationHeader, commandMetadata.shell); + const controller = new {ControllerName}(client); + + try { + const response = await controller.{sdkMethod}( + this.CONTENT_TYPE, + file + ); + return ok(response.result); + } catch (error) { + if (error instanceof ProblemDetailsError) { + const message = Object.values(error.result!.errors as Record)[0]?.[0] ?? null; + const errorMessage = error.result!.title + "\n- " + message; + if (error.statusCode === 400) { + return err(ServiceError.badRequest(errorMessage)); + } + if (error.statusCode === 403) { + return err(ServiceError.forbidden(errorMessage)); + } + } + return err(handleServiceError(error)); + } finally { + fileStream.close(); + } + } + + private createAuthorizationHeader = (authInfo: AuthInfo | null, overrideAuthKey: string | null): string => { + const key = overrideAuthKey || authInfo?.authKey; + return `X-Auth-Key ${key ?? ""}`; + }; +} +``` + +### Raw Axios Service Template (with auth) + +**Use when:** calling APIMatic REST endpoints directly without the SDK client. + +**Based on:** `src/infrastructure/services/api-service.ts`, `src/infrastructure/services/auth-service.ts` + +```typescript +import axios from "axios"; +import { AuthInfo, getAuthInfo } from "../../client-utils/auth-manager.js"; +import { DirectoryPath } from "../../types/file/directoryPath.js"; +import { envInfo } from "../env-info.js"; +import { err, ok, Result } from "neverthrow"; +import { handleServiceError, ServiceError } from "../service-error.js"; + +export class {PascalName}Service { + private readonly apiBaseUrl = "https://api.apimatic.io" as const; + + public async {methodName}( + configDir: DirectoryPath, + shell: string, + authKey: string | null + ): Promise> { + const authInfo: AuthInfo | null = await getAuthInfo(configDir.toString()); + if (authInfo === null && !authKey) { + return err(ServiceError.UnAuthorized); + } + + try { + const token = authKey || authInfo?.authKey; + const response = await this.axiosInstance(shell, token).get("/{endpoint}"); + + if (response.status === 200) { + return ok(response.data as {ResponseType}); + } + return err(ServiceError.InvalidResponse); + } catch (error: unknown) { + return err(handleServiceError(error)); + } + } + + private axiosInstance(shell: string, apiKey: string | undefined) { + const headers: Record = { + "User-Agent": envInfo.getUserAgent(shell) + }; + + if (apiKey) { + headers.Authorization = `X-Auth-Key ${apiKey}`; + } + + return axios.create({ + baseURL: envInfo.getBaseUrl() ?? this.apiBaseUrl, + headers + }); + } +} +``` + +### Stateless Axios Service Template (no auth) + +**Use when:** making external HTTP calls that don't require APIMatic authentication. + +**Based on:** `src/infrastructure/services/file-download-service.ts` + +```typescript +import axios from "axios"; +import { err, ok, Result } from "neverthrow"; +import { handleServiceError, ServiceError } from "../service-error.js"; + +export class {PascalName}Service { + public async {methodName}( + /* parameters */ + ): Promise> { + try { + const response = await axios.get(/* url */, { + /* request config */ + }); + + if (/* success condition */) { + return ok(/* parsed result */); + } + return err(ServiceError.InvalidResponse); + } catch (error: unknown) { + return err(handleServiceError(error)); + } + } +} +``` diff --git a/.ai/skills/value-object.md b/.ai/skills/value-object.md new file mode 100644 index 00000000..074ec25e --- /dev/null +++ b/.ai/skills/value-object.md @@ -0,0 +1,261 @@ +# Value Object (Rich Class) Conventions + +Value objects live at `src/types/file/` and wrap a single primitive (`string`, `number`) that has domain meaning. They enforce their own invariants, expose only domain-meaningful operations, and hide the underlying primitive entirely. The primitive surfaces only at infrastructure boundaries — never within the domain, application, or action layers. + +## Conventions + +### Encapsulation — DO + +- **DO** declare the wrapped primitive as `private readonly` — the only way out is `toString()`. +- **DO** implement `toString()` as the sole escape hatch from rich type to raw primitive. +- **DO** let `toString()` be called implicitly by template literals — callers write `` `Output: ${dir}` `` without an explicit `.toString()`. +- **DO** have methods that produce derived values return rich types — a method extracting a leaf name returns `FileName`, not `string`. +- **DO** compose rich types through each other — `FilePath` holds `DirectoryPath` and `FileName` as objects; both are passed as objects in constructors and methods. +- **DO** use `static create()` returning `T | undefined` for construction from user input or external data that can fail validation. +- **DO** use `static createInput()` for the flag-value-to-rich-type pattern when a sensible fallback exists (e.g. `DirectoryPath.default`). +- **DO** use `static readonly` for well-known singleton instances (e.g., `DirectoryPath.default`). +- **DO** access the private field directly inside methods — write `this.directoryPath` not `this.toString()`. + +### Encapsulation — DON'T + +- **DON'T** expose a getter that returns the underlying primitive: + ```ts + // BANNED + get path(): string { return this.directoryPath; } + ``` +- **DON'T** call `.toString()` on `this` inside any method other than `toString()` itself. +- **DON'T** call `.toString()` on a rich object when passing it to another rich object's constructor or method: + ```ts + // BANNED + new FilePath(new DirectoryPath(dir.toString()), new FileName(name.toString())) + + // CORRECT + new FilePath(dir, name) + ``` +- **DON'T** unwrap to perform path arithmetic and re-wrap the result: + ```ts + // BANNED + new DirectoryPath(this.directoryPath + "/subdir") + + // CORRECT — use the rich method + this.join("subdir") + ``` +- **DON'T** return `string` from a method whose result has a domain type: + ```ts + // BANNED + public leafName(): string { return path.basename(this.directoryPath); } + + // CORRECT + public leafName(): FileName { return new FileName(path.basename(this.directoryPath)); } + ``` +- **DON'T** use `.toString()` inside `f.path()`, `f.link()`, or `f.var()` helpers — those accept the rich type via template interpolation. +- **DON'T** throw from the constructor when construction can fail — use `static create()` returning `T | undefined`. + +### toString() boundary rule + +`.toString()` — including implicit unwrapping via template literals — is permitted **only** at these boundary sites: + +| Boundary | Example | +|---|---| +| `fs.*` / `fsExtra.*` calls | `fsExtra.stat(filePath.toString())` | +| `path.*` calls | `path.join(this.directoryPath, ...subPath)` | +| Axios / HTTP request construction | `axios.get(url.toString())` | +| External library APIs (execa, archiver, extract-zip, chokidar) | `execa("code", [filePath.toString()])` | +| Template literal inside own `toString()` | `` `${this.directoryPath}/${this.fileName}` `` | +| Logging / terminal display | `` log.info(`Saved to ${dir}`) `` | + +Anything not in this table is a violation. In particular: domain methods, context classes, action classes, and application classes must not unwrap to `string` outside of `toString()` itself. + +### Composition — DO + +- **DO** pass `DirectoryPath` and `FileName` objects as constructor arguments — never pass raw strings. +- **DO** use `directory.join("subdir")` to derive a subdirectory — it returns `DirectoryPath`. +- **DO** use `filePath.replaceDirectory(newDirectory)` to move a file to a new parent — no string reconstruction. +- **DO** call `directoryPath.isEqual(other)` for equality — never `a.toString() === b.toString()`. + +### Context class exposure rule + +Context classes (`PortalContext`, `SdkContext`, etc.) may expose a rich-type field via a public getter or method **only** when the caller needs it for **logging or prompting display** — never for path computation, I/O, or construction of other paths. Add an inline comment when you do: + +```ts +// Exposed for display in prompts — callers must not use this for path computation +public get sdkLanguageDirectory(): DirectoryPath { + return this.languageDirectory; +} +``` + +--- + +## Known Gaps in Existing Code + +These patterns exist in the current codebase and are flagged for awareness: + +| File | Issue | +|---|---| +| `src/types/file/directoryPath.ts:31` | `leafName()` returns `string` — should return `FileName` | +| `src/types/sdk-context.ts` | `sdkLanguageDirectory` getter exposes `DirectoryPath` — needs justification comment if kept | +| `src/types/toc-context.ts` | `tocPath` getter exposes `FilePath` — prefer returning it only from the `save()` operation result | + +--- + +## Review Checklist + +- [ ] All imports use `.js` extension (e.g., `./directoryPath.js`) +- [ ] The wrapped primitive is `private readonly` — no public field, no getter exposing it +- [ ] No method calls `this.toString()` — methods access `this.{field}` directly +- [ ] `toString()` is the only method that returns the raw primitive +- [ ] Methods returning derived values return a rich type, not `string` +- [ ] Composed types receive other rich objects in their constructors — no `.toString()` at call sites +- [ ] `join()` / `replaceDirectory()` / similar transformation methods return rich types +- [ ] `static create()` used for user-input construction that can fail — constructor is for trusted callers +- [ ] `directoryPath.isEqual(other)` used for equality — not `a.toString() === b.toString()` +- [ ] No `.toString()` call appears outside a boundary site (fs, path, axios, external lib, logging) +- [ ] Context class public getter (if any) has an inline comment justifying the exposure (display only) +- [ ] No `.toString()` passed to `f.path()`, `f.link()`, or `f.var()` helpers +- [ ] `static readonly` used for constant singleton instances + +--- + +## Reference Files + +| Pattern | File | +|---|---| +| Ideal simple wrapper — private field, `static create()`, `toString()` only | `src/types/file/urlPath.ts` | +| `static createInput()` fallback, rich join/equality methods | `src/types/file/directoryPath.ts` | +| Rich predicate (`isMarkDown`) and transform (`normalize`) returning rich type | `src/types/file/fileName.ts` | +| Composed value object — holds two rich types, `static create()` splits at boundary | `src/types/file/filePath.ts` | +| Correct boundary unwrapping — all `.toString()` calls at `fs.*` / `path.*` sites | `src/infrastructure/file-service.ts` | +| Context with flagged public getter (display use) | `src/types/sdk-context.ts` | + +--- + +## Scaffolding + +### What to determine + +1. **Name** — PascalCase class name (e.g., `PortNumber`, `ApiVersion`, `RecipeName`) +2. **Primitive type** — the underlying primitive (`string`, `number`, etc.) +3. **Validation** — can construction fail from user input? If yes, add `static create()` +4. **Fallback** — is there a sensible default for flag values? If yes, add `static createInput()` + `static readonly default` +5. **Derived values** — what can be computed? Each result that has domain meaning should be a rich type +6. **Composition** — does this hold other rich types as fields? +7. **Equality** — is structural equality needed? Add `isEqual(other: T)` + +--- + +### Simple Value Object (wraps a single primitive) + +**Use when:** wrapping a string or number that has a domain name — URL, port, version, file name, recipe name. + +**Path:** `src/types/file/{name}.ts` for file-domain types; `src/types/{domain}/{name}.ts` for others. + +```ts +export class {ClassName} { + private readonly {field}: {Primitive}; + + constructor({field}: {Primitive}) { + this.{field} = {field}; + } + + // Static factory for user-input / external data that can fail validation. + // Returns undefined — callers must check before using. + public static create({field}: {Primitive}): {ClassName} | undefined { + if (!{field} || !{validationCondition}) { + return undefined; + } + return new {ClassName}({field}); + } + + // Well-known constant (if applicable) + // public static readonly default = new {ClassName}({defaultValue}); + + // Static factory for flag values with fallback (if applicable) + // public static createInput(input: {Primitive} | undefined): {ClassName} { + // return input ? new {ClassName}(input) : {ClassName}.default; + // } + + // Domain predicate — returns boolean, never exposes primitive + public {isDomainCondition}(): boolean { + return this.{field}.{check}; + } + + // Derived value — returns a rich type, never string + public {derivedValue}(): {RichReturnType} { + const raw = /* compute from this.{field} */; + return new {RichReturnType}(raw); + } + + // Structural equality — compare field directly, not via toString() + public isEqual(other: {ClassName}): boolean { + return this.{field} === other.{field}; + } + + // Sole escape hatch — called implicitly by template literals at boundaries + public toString(): {Primitive} { + return this.{field}; + } +} +``` + +**Notes:** +- All methods access `this.{field}` directly — they never call `this.toString()`. +- The direct `constructor` is for trusted callers (e.g., another rich type creating a derived instance). `static create()` is the entry point from external/user data. +- `isEqual()` compares the private field directly — never calls `a.toString() === b.toString()`. + +--- + +### Composed Value Object (holds other rich types) + +**Use when:** the value object is built from two or more rich types — e.g., `FilePath` = `DirectoryPath` + `FileName`. + +**Path:** `src/types/file/{name}.ts` or the appropriate domain folder. + +```ts +import { {PartA} } from "./{partA}.js"; +import { {PartB} } from "./{partB}.js"; + +export class {ClassName} { + private readonly {partA}: {PartA}; + private readonly {partB}: {PartB}; + + // Constructor takes rich types — callers must not pass raw strings + constructor({partA}: {PartA}, {partB}: {PartB}) { + this.{partA} = {partA}; + this.{partB} = {partB}; + } + + // Static factory for user-input: splits the raw string at the boundary (path.* calls), + // then wraps each part in its rich type + public static create(rawValue: string): {ClassName} | undefined { + if (!rawValue) { + return undefined; + } + try { + // path.* calls are a legitimate boundary — they produce the raw parts + const rawA = /* path.dirname / path.basename / etc. */; + const rawB = /* path.basename / path.extname / etc. */; + return new {ClassName}(new {PartA}(rawA), new {PartB}(rawB)); + } catch { + return undefined; + } + } + + // Transformation — swap one component, keep the other; returns a new rich type + public replace{PartA}(new{PartA}: {PartA}): {ClassName} { + return new {ClassName}(new{PartA}, this.{partB}); + } + + // toString() joins components at the infrastructure boundary. + // Template literal calls each component's toString() implicitly — that is correct here. + public toString(): string { + return `${this.{partA}}/${this.{partB}}`; + // Or: path.join(this.{partA}.toString(), this.{partB}.toString()) + // when OS-correct separators are required (path.* inside toString() is a boundary site) + } +} +``` + +**Notes:** +- Constructor signature uses the rich types — a caller with a raw string must run `static create()` first. +- `replace{PartA}()` receives and returns rich types — never accepts or returns strings. +- Component parts unwrap inside `toString()` via template literal; this is the one permitted place they call `.toString()`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..e1c17fc7 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,17 @@ +# CLAUDE.md + +This file provides guidance to Claude Code when working in this repository. + +Read `.ai/instructions.md` for full project instructions (architecture, conventions, testing, commits). + +## Skills + +Reference these files as needed for scaffolding: + +- `.ai/skills/command.md` — Command + Action + Prompts conventions and scaffolding +- `.ai/skills/action.md` — Action class conventions and scaffolding +- `.ai/skills/context.md` — Context object conventions and scaffolding +- `.ai/skills/prompt.md` — Prompts class conventions and scaffolding +- `.ai/skills/service.md` — Infrastructure Service conventions and scaffolding +- `.ai/skills/value-object.md` — Value object (rich class) conventions: encapsulation, boundary unwrapping, composition +- `.ai/skills/event.md` — Domain event conventions: past-tense naming, variants, where to fire diff --git a/package-lock.json b/package-lock.json index e98fe599..72b2621d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@apimatic/cli", - "version": "1.1.0-beta.9", + "version": "1.1.0-beta.10-test", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@apimatic/cli", - "version": "1.1.0-beta.9", + "version": "1.1.0-beta.10-test", "license": "MIT", "dependencies": { - "@apimatic/sdk": "^0.2.0-alpha.7", + "@apimatic/sdk": "file:./apimatic-sdk-0.2.0-alpha.8-test.tgz", "@clack/prompts": "1.0.0-alpha.1", "@oclif/core": "^4.2.8", "@oclif/plugin-autocomplete": "^3.2.24", @@ -27,6 +27,7 @@ "fs-extra": "^11.3.0", "get-port": "^7.1.0", "is-in-ci": "^2.0.0", + "isomorphic-git": "^1.37.2", "livereload": "^0.9.3", "neverthrow": "^8.2.0", "open": "^8.4.0", @@ -87,8 +88,6 @@ }, "node_modules/@apimatic/authentication-adapters": { "version": "0.5.14", - "resolved": "https://registry.npmjs.org/@apimatic/authentication-adapters/-/authentication-adapters-0.5.14.tgz", - "integrity": "sha512-V7nhHShPrU8LfjKKHoVJNS50SveSL77CexVuS4aeQyXx99HwdQVJwl2MK0KAYM6/b2ufQbJ7Eee2fzQT0TVXSQ==", "license": "MIT", "dependencies": { "@apimatic/core-interfaces": "^0.2.14", @@ -102,8 +101,6 @@ }, "node_modules/@apimatic/axios-client-adapter": { "version": "0.3.21", - "resolved": "https://registry.npmjs.org/@apimatic/axios-client-adapter/-/axios-client-adapter-0.3.21.tgz", - "integrity": "sha512-pr/XvAvH9FjbpwM+B7vHQxM7alocOX1kLNtSpXKW3yxTYxksF3ydnUuQ85rRbCoNpyfMOIjnRBCNUBzX5p2Hnw==", "license": "MIT", "dependencies": { "@apimatic/convert-to-stream": "^0.1.9", @@ -127,8 +124,6 @@ }, "node_modules/@apimatic/convert-to-stream": { "version": "0.1.9", - "resolved": "https://registry.npmjs.org/@apimatic/convert-to-stream/-/convert-to-stream-0.1.9.tgz", - "integrity": "sha512-C9NEKnDZoTRBRVeUGXVyAEmy6P5o+8oLwEckTKj0iBlExJLEXNt14nf4wxfzRO1KR8j5Bw8S6yStKCrQzcVERA==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -139,8 +134,6 @@ }, "node_modules/@apimatic/core": { "version": "0.10.29", - "resolved": "https://registry.npmjs.org/@apimatic/core/-/core-0.10.29.tgz", - "integrity": "sha512-QhORiq0QbjlDMrw8ZZsAeG2DzE6QgGz5ukD5w2MOWE/3iIWnUDEROjmc2SfhyiGsE3GoEJ8cyhMMdjlOioP6ww==", "license": "MIT", "dependencies": { "@apimatic/convert-to-stream": "^0.1.9", @@ -164,8 +157,6 @@ }, "node_modules/@apimatic/core-interfaces": { "version": "0.2.14", - "resolved": "https://registry.npmjs.org/@apimatic/core-interfaces/-/core-interfaces-0.2.14.tgz", - "integrity": "sha512-PQmSU32ndxtDddMCjbkNY/sVvDwQAsHUGKrdG5aGVE7iw/qvB2Tm2zyCarOB5TlDr4OB+/tuLCVhji0icx6MHg==", "license": "MIT", "dependencies": { "@apimatic/file-wrapper": "^0.3.9", @@ -178,8 +169,6 @@ }, "node_modules/@apimatic/file-wrapper": { "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@apimatic/file-wrapper/-/file-wrapper-0.3.9.tgz", - "integrity": "sha512-Fh3UE7UPs2v4wkJdsD+uJFF147+7X0qkQfKBdeLZx6mZ5RmBJOBbS6ApvstQTV279YsHiiedKUZGJ6XLoVU+pQ==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -190,8 +179,6 @@ }, "node_modules/@apimatic/http-headers": { "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@apimatic/http-headers/-/http-headers-0.3.8.tgz", - "integrity": "sha512-ShvCuT39hYfBTI+H1I16m5i6XZCyUy2kQJ6Jhfj78TwsW5r6AyCbzW7DEro8GN2nNYRU1+E/hrgH6J85YmriOA==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -202,8 +189,6 @@ }, "node_modules/@apimatic/http-query": { "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@apimatic/http-query/-/http-query-0.3.9.tgz", - "integrity": "sha512-D6nqXcCR3P6iWbJ9uFXyyF2z1PEhTbGFbHNNuwF1NQ4tnThQk67DW9ou7/XcWi21zLh9MUchDWw9I0iE+5F2xA==", "license": "MIT", "dependencies": { "@apimatic/core-interfaces": "^0.2.14", @@ -216,14 +201,10 @@ }, "node_modules/@apimatic/json-bigint": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@apimatic/json-bigint/-/json-bigint-1.2.0.tgz", - "integrity": "sha512-+bmVzYMdZu0Ya5L+my4FXFUih54OvQA/qlZsFOYdOoostyUuB27UDrVWQs/WVCmS0ADdo5vTU0eeTrrBkHoySw==", "license": "MIT" }, "node_modules/@apimatic/proxy": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@apimatic/proxy/-/proxy-0.1.4.tgz", - "integrity": "sha512-Vzgfu7wcA5aEJyj2SjQ00Tb06fhBof8gDo1kSsF6sZBm4QjdFywN5AMbQwhfFOKjHqcsNmJspdeqcdymUQ77jA==", "license": "MIT", "dependencies": { "http-proxy-agent": "^7.0.2", @@ -235,8 +216,6 @@ }, "node_modules/@apimatic/schema": { "version": "0.7.21", - "resolved": "https://registry.npmjs.org/@apimatic/schema/-/schema-0.7.21.tgz", - "integrity": "sha512-RCke4toXjA7fBRxQVa1GR+Lj9utVOEJ3voDI26dhk+bZuAac4UXPzkTEaIO3AIe/o8pcKCOkpNIzhzm57Cv2Qg==", "license": "MIT", "dependencies": { "tslib": "^2.8.1" @@ -246,9 +225,9 @@ } }, "node_modules/@apimatic/sdk": { - "version": "0.2.0-alpha.7", - "resolved": "https://registry.npmjs.org/@apimatic/sdk/-/sdk-0.2.0-alpha.7.tgz", - "integrity": "sha512-7cfgY27qNIzKxjP6ebJm9O9/NRoJVvhBAg0VzBelI6CWF17RotgWuTaLgjewVyvfSDT4caUOqDMk7rLJBmZ+jA==", + "version": "0.2.0-alpha.8-test", + "resolved": "file:apimatic-sdk-0.2.0-alpha.8-test.tgz", + "integrity": "sha512-YLnPmaML5wOJbRgvWOLvl3udRvM/0rdq9ZWg9vAxHRK4I3zB59tBD+utj037lxW4MG+rEQgUwyaBvjsHIlPaFQ==", "license": "MIT", "dependencies": { "@apimatic/authentication-adapters": "^0.5.14", @@ -277,8 +256,6 @@ }, "node_modules/@aws-crypto/crc32c": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", - "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1389,8 +1366,6 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -1409,8 +1384,6 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -1497,8 +1470,6 @@ }, "node_modules/@clack/core": { "version": "1.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.0.0-alpha.1.tgz", - "integrity": "sha512-rFbCU83JnN7l3W1nfgCqqme4ZZvTTgsiKQ6FM0l+r0P+o2eJpExcocBUWUIwnDzL76Aca9VhUdWmB2MbUv+Qyg==", "license": "MIT", "dependencies": { "picocolors": "^1.0.0", @@ -1507,8 +1478,6 @@ }, "node_modules/@clack/prompts": { "version": "1.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.0.0-alpha.1.tgz", - "integrity": "sha512-07MNT0OsxjKOcyVfX8KhXBhJiyUbDP1vuIAcHc+nx5v93MJO23pX3X/k3bWz6T3rpM9dgWPq90i4Jq7gZAyMbw==", "license": "MIT", "dependencies": { "@clack/core": "1.0.0-alpha.1", @@ -1518,8 +1487,6 @@ }, "node_modules/@colors/colors": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, "license": "MIT", "optional": true, @@ -1759,8 +1726,6 @@ }, "node_modules/@commitlint/rules/node_modules/execa": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", "dependencies": { @@ -1796,8 +1761,6 @@ }, "node_modules/@commitlint/rules/node_modules/human-signals": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1806,8 +1769,6 @@ }, "node_modules/@commitlint/rules/node_modules/npm-run-path": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { @@ -1826,8 +1787,6 @@ }, "node_modules/@commitlint/rules/node_modules/strip-final-newline": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { @@ -1872,8 +1831,6 @@ }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "license": "MIT", "dependencies": { @@ -1885,8 +1842,6 @@ }, "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2445,8 +2400,6 @@ }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2526,8 +2479,6 @@ }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2730,8 +2681,6 @@ }, "node_modules/@inquirer/checkbox/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -2750,8 +2699,6 @@ }, "node_modules/@inquirer/checkbox/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -2763,8 +2710,6 @@ }, "node_modules/@inquirer/checkbox/node_modules/wrap-ansi": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "license": "MIT", "dependencies": { @@ -2946,8 +2891,6 @@ }, "node_modules/@inquirer/editor/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -2966,8 +2909,6 @@ }, "node_modules/@inquirer/editor/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -2979,8 +2920,6 @@ }, "node_modules/@inquirer/editor/node_modules/wrap-ansi": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "license": "MIT", "dependencies": { @@ -3063,8 +3002,6 @@ }, "node_modules/@inquirer/expand/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -3083,8 +3020,6 @@ }, "node_modules/@inquirer/expand/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -3096,8 +3031,6 @@ }, "node_modules/@inquirer/expand/node_modules/wrap-ansi": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "license": "MIT", "dependencies": { @@ -3242,8 +3175,6 @@ }, "node_modules/@inquirer/number/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -3262,8 +3193,6 @@ }, "node_modules/@inquirer/number/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -3275,8 +3204,6 @@ }, "node_modules/@inquirer/number/node_modules/wrap-ansi": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "license": "MIT", "dependencies": { @@ -3359,8 +3286,6 @@ }, "node_modules/@inquirer/password/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -3379,8 +3304,6 @@ }, "node_modules/@inquirer/password/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -3392,8 +3315,6 @@ }, "node_modules/@inquirer/password/node_modules/wrap-ansi": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "license": "MIT", "dependencies": { @@ -3552,8 +3473,6 @@ }, "node_modules/@inquirer/prompts/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -3572,8 +3491,6 @@ }, "node_modules/@inquirer/prompts/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -3585,8 +3502,6 @@ }, "node_modules/@inquirer/prompts/node_modules/wrap-ansi": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "license": "MIT", "dependencies": { @@ -3669,8 +3584,6 @@ }, "node_modules/@inquirer/rawlist/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -3689,8 +3602,6 @@ }, "node_modules/@inquirer/rawlist/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -3702,8 +3613,6 @@ }, "node_modules/@inquirer/rawlist/node_modules/wrap-ansi": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "license": "MIT", "dependencies": { @@ -3787,8 +3696,6 @@ }, "node_modules/@inquirer/search/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -3807,8 +3714,6 @@ }, "node_modules/@inquirer/search/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -3820,8 +3725,6 @@ }, "node_modules/@inquirer/search/node_modules/wrap-ansi": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "license": "MIT", "dependencies": { @@ -4156,7 +4059,7 @@ "dependencies": { "@oclif/core": "^4", "ansis": "^3.16.0", - "debug": "^4.4.1", + "debug": "^4.4.0", "ejs": "^3.1.10" }, "engines": { @@ -4228,8 +4131,6 @@ }, "node_modules/@octokit/auth-token": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", - "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", "dev": true, "license": "MIT", "peer": true, @@ -4415,15 +4316,11 @@ }, "node_modules/@open-draft/deferred-promise": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", - "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", "dev": true, "license": "MIT" }, "node_modules/@open-draft/logger": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", - "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4433,8 +4330,6 @@ }, "node_modules/@open-draft/until": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", - "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", "dev": true, "license": "MIT" }, @@ -4515,14 +4410,10 @@ }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", "license": "MIT" }, "node_modules/@semantic-release/changelog": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.3.tgz", - "integrity": "sha512-dZuR5qByyfe3Y03TpmCvAxCyTnp7r5XwtHRf/8vD9EAn4ZWbavUX8adMtXYzE86EVh0gyLA7lm5yW4IV30XUag==", "dev": true, "license": "MIT", "dependencies": { @@ -4540,8 +4431,6 @@ }, "node_modules/@semantic-release/commit-analyzer": { "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.1.tgz", - "integrity": "sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ==", "dev": true, "license": "MIT", "peer": true, @@ -4596,8 +4485,6 @@ }, "node_modules/@semantic-release/commit-analyzer/node_modules/meow": { "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, "license": "MIT", "peer": true, @@ -4610,8 +4497,6 @@ }, "node_modules/@semantic-release/error": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", - "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", "dev": true, "license": "MIT", "engines": { @@ -4620,8 +4505,6 @@ }, "node_modules/@semantic-release/git": { "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz", - "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==", "dev": true, "license": "MIT", "dependencies": { @@ -4643,8 +4526,6 @@ }, "node_modules/@semantic-release/git/node_modules/execa": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", "dependencies": { @@ -4680,8 +4561,6 @@ }, "node_modules/@semantic-release/git/node_modules/human-signals": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4690,8 +4569,6 @@ }, "node_modules/@semantic-release/git/node_modules/npm-run-path": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { @@ -4710,8 +4587,6 @@ }, "node_modules/@semantic-release/git/node_modules/strip-final-newline": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { @@ -4752,8 +4627,6 @@ }, "node_modules/@semantic-release/github/node_modules/@semantic-release/error": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", - "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, "license": "MIT", "peer": true, @@ -4763,8 +4636,6 @@ }, "node_modules/@semantic-release/github/node_modules/aggregate-error": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", - "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, "license": "MIT", "peer": true, @@ -4798,8 +4669,6 @@ }, "node_modules/@semantic-release/github/node_modules/escape-string-regexp": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "license": "MIT", "peer": true, @@ -4812,8 +4681,6 @@ }, "node_modules/@semantic-release/github/node_modules/indent-string": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "license": "MIT", "peer": true, @@ -4826,8 +4693,6 @@ }, "node_modules/@semantic-release/npm": { "version": "12.0.2", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.2.tgz", - "integrity": "sha512-+M9/Lb35IgnlUO6OSJ40Ie+hUsZLuph2fqXC/qrKn0fMvUU/jiCjpoL6zEm69vzcmaZJ8yNKtMBEKHWN49WBbQ==", "dev": true, "license": "MIT", "peer": true, @@ -4855,8 +4720,6 @@ }, "node_modules/@semantic-release/npm/node_modules/@semantic-release/error": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", - "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, "license": "MIT", "peer": true, @@ -4866,8 +4729,6 @@ }, "node_modules/@semantic-release/npm/node_modules/aggregate-error": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", - "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, "license": "MIT", "peer": true, @@ -4901,8 +4762,6 @@ }, "node_modules/@semantic-release/npm/node_modules/escape-string-regexp": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "license": "MIT", "peer": true, @@ -4915,8 +4774,6 @@ }, "node_modules/@semantic-release/npm/node_modules/hosted-git-info": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, "license": "ISC", "peer": true, @@ -4929,8 +4786,6 @@ }, "node_modules/@semantic-release/npm/node_modules/indent-string": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "license": "MIT", "peer": true, @@ -4943,16 +4798,12 @@ }, "node_modules/@semantic-release/npm/node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC", "peer": true }, "node_modules/@semantic-release/npm/node_modules/normalize-package-data": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -4967,8 +4818,6 @@ }, "node_modules/@semantic-release/npm/node_modules/parse-json": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", - "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "dev": true, "license": "MIT", "peer": true, @@ -4986,8 +4835,6 @@ }, "node_modules/@semantic-release/npm/node_modules/read-pkg": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", - "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", "dev": true, "license": "MIT", "peer": true, @@ -5007,8 +4854,6 @@ }, "node_modules/@semantic-release/npm/node_modules/type-fest": { "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, "license": "(MIT OR CC0-1.0)", "peer": true, @@ -5021,8 +4866,6 @@ }, "node_modules/@semantic-release/npm/node_modules/unicorn-magic": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", "dev": true, "license": "MIT", "peer": true, @@ -5035,8 +4878,6 @@ }, "node_modules/@semantic-release/release-notes-generator": { "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.1.0.tgz", - "integrity": "sha512-CcyDRk7xq+ON/20YNR+1I/jP7BYKICr1uKd1HHpROSnnTdGqOTburi4jcRiTYz0cpfhxSloQO3cGhnoot7IEkA==", "dev": true, "license": "MIT", "peer": true, @@ -5093,8 +4934,6 @@ }, "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", - "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", "dev": true, "license": "MIT", "peer": true, @@ -5107,8 +4946,6 @@ }, "node_modules/@semantic-release/release-notes-generator/node_modules/meow": { "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, "license": "MIT", "peer": true, @@ -5148,8 +4985,6 @@ }, "node_modules/@sindresorhus/merge-streams": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "license": "MIT", "engines": { "node": ">=18" @@ -5160,8 +4995,6 @@ }, "node_modules/@sinonjs/commons": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5170,8 +5003,6 @@ }, "node_modules/@sinonjs/commons/node_modules/type-detect": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, "license": "MIT", "engines": { @@ -6004,29 +5835,21 @@ }, "node_modules/@tsconfig/node12": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true, "license": "MIT" }, "node_modules/@types/archiver": { "version": "5.3.4", - "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.3.4.tgz", - "integrity": "sha512-Lj7fLBIMwYFgViVVZHEdExZC3lVYsl+QL0VmdNdIzGZH544jHveYWij6qdnBgJQDnR7pMKliN9z2cPZFEbhyPw==", "dev": true, "license": "MIT", "dependencies": { @@ -6053,8 +5876,6 @@ }, "node_modules/@types/chai": { "version": "4.3.20", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", - "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", "dev": true, "license": "MIT" }, @@ -6070,8 +5891,6 @@ }, "node_modules/@types/connect-livereload": { "version": "0.5.32", - "resolved": "https://registry.npmjs.org/@types/connect-livereload/-/connect-livereload-0.5.32.tgz", - "integrity": "sha512-2x/n+cMGdnumlUyktVY2qrZn+OMBjrmmFEAZlbe3AXG5nk3qguvdw3vVvd54orFLd3UrmV7vOK7vcXlqzGEf2w==", "dev": true, "license": "MIT", "dependencies": { @@ -6113,15 +5932,11 @@ }, "node_modules/@types/fast-levenshtein": { "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@types/fast-levenshtein/-/fast-levenshtein-0.0.4.tgz", - "integrity": "sha512-tkDveuitddQCxut1Db8eEFfMahTjOumTJGPHmT9E7KUH+DkVq9WTpVvlfenf3S+uCBeu8j5FP2xik/KfxOEjeA==", "dev": true, "license": "MIT" }, "node_modules/@types/fs-extra": { "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", - "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", "dev": true, "license": "MIT", "dependencies": { @@ -6144,8 +5959,6 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, "license": "MIT" }, @@ -6158,8 +5971,6 @@ }, "node_modules/@types/livereload": { "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@types/livereload/-/livereload-0.9.5.tgz", - "integrity": "sha512-2RXcRKdivPmn67pwjytvHoRv46AeXaLYVUWA0zkel1XSAOH5i71G0KfUdE5u3g80T155gR3Fo3ilVaqparLsVA==", "dev": true, "license": "MIT", "dependencies": { @@ -6189,8 +6000,6 @@ }, "node_modules/@types/mock-fs": { "version": "4.13.4", - "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.4.tgz", - "integrity": "sha512-mXmM0o6lULPI8z3XNnQCpL0BGxPwx1Ul1wXYEPBGl4efShyxW2Rln0JOPEWGyZaYZMM6OVXM/15zUuFMY52ljg==", "dev": true, "license": "MIT", "dependencies": { @@ -6233,8 +6042,6 @@ }, "node_modules/@types/prettier": { "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", - "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", "dev": true, "license": "MIT" }, @@ -6297,8 +6104,6 @@ }, "node_modules/@types/sinon": { "version": "17.0.4", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.4.tgz", - "integrity": "sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew==", "dev": true, "license": "MIT", "dependencies": { @@ -6314,8 +6119,6 @@ }, "node_modules/@types/treeify": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/treeify/-/treeify-1.0.3.tgz", - "integrity": "sha512-hx0o7zWEUU4R2Amn+pjCBQQt23Khy/Dk56gQU5xi5jtPL1h83ACJCeFaB2M/+WO1AntvWrSoVnnCAfI1AQH4Cg==", "dev": true, "license": "MIT" }, @@ -6348,8 +6151,6 @@ }, "node_modules/@types/yauzl": { "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "license": "MIT", "optional": true, "dependencies": { @@ -6665,8 +6466,6 @@ }, "node_modules/agent-base": { "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", "engines": { "node": ">= 14" @@ -6776,8 +6575,6 @@ }, "node_modules/any-promise": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "dev": true, "license": "MIT", "peer": true @@ -6867,8 +6664,6 @@ }, "node_modules/argv-formatter": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", - "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", "dev": true, "license": "MIT", "peer": true @@ -6910,14 +6705,12 @@ "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" }, "engines": { "node": ">= 0.4" @@ -6933,13 +6726,12 @@ "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", + "es-abstract": "^1.23.2", "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -7061,10 +6853,14 @@ "node": ">= 0.4" } }, + "node_modules/async-lock": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", + "license": "MIT" + }, "node_modules/async-mutex": { "version": "0.5.0", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", - "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", "license": "MIT", "dependencies": { "tslib": "^2.4.0" @@ -7090,7 +6886,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" @@ -7258,8 +7053,6 @@ }, "node_modules/before-after-hook": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", - "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", "dev": true, "license": "Apache-2.0", "peer": true @@ -7317,8 +7110,6 @@ }, "node_modules/bottleneck": { "version": "2.19.5", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", - "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", "dev": true, "license": "MIT", "peer": true @@ -7506,7 +7297,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", @@ -7634,8 +7424,6 @@ }, "node_modules/chai": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, "license": "MIT", "dependencies": { @@ -7704,8 +7492,6 @@ }, "node_modules/char-regex": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, "license": "MIT", "peer": true, @@ -7757,6 +7543,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/clean-git-ref": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz", + "integrity": "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==", + "license": "Apache-2.0" + }, "node_modules/clean-stack": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", @@ -7787,8 +7579,6 @@ }, "node_modules/cli-highlight": { "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", "dev": true, "license": "ISC", "peer": true, @@ -7810,8 +7600,6 @@ }, "node_modules/cli-highlight/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "peer": true, @@ -7821,8 +7609,6 @@ }, "node_modules/cli-highlight/node_modules/cliui": { "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "license": "ISC", "peer": true, @@ -7834,8 +7620,6 @@ }, "node_modules/cli-highlight/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "peer": true, @@ -7848,8 +7632,6 @@ }, "node_modules/cli-highlight/node_modules/yargs": { "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "license": "MIT", "peer": true, @@ -7880,8 +7662,6 @@ }, "node_modules/cli-table3": { "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dev": true, "license": "MIT", "peer": true, @@ -8153,8 +7933,6 @@ }, "node_modules/conventional-changelog-writer/node_modules/meow": { "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, "license": "MIT", "peer": true, @@ -8181,8 +7959,6 @@ }, "node_modules/conventional-commits-filter": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", - "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", "dev": true, "license": "MIT", "peer": true, @@ -8213,8 +7989,6 @@ }, "node_modules/convert-hrtime": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", - "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", "dev": true, "license": "MIT", "peer": true, @@ -8272,8 +8046,6 @@ }, "node_modules/cosmiconfig/node_modules/yaml": { "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", "dev": true, "license": "ISC", "engines": { @@ -8328,8 +8100,6 @@ }, "node_modules/crypto-random-string": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", "dev": true, "license": "MIT", "peer": true, @@ -8345,8 +8115,6 @@ }, "node_modules/crypto-random-string/node_modules/type-fest": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, "license": "(MIT OR CC0-1.0)", "peer": true, @@ -8479,7 +8247,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, "license": "MIT", "dependencies": { "mimic-response": "^3.1.0" @@ -8495,7 +8262,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -8519,8 +8285,6 @@ }, "node_modules/deep-extend": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "license": "MIT", "peer": true, @@ -8565,7 +8329,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -8636,8 +8399,6 @@ }, "node_modules/detect-browser": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz", - "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==", "license": "MIT" }, "node_modules/detect-indent": { @@ -8668,24 +8429,24 @@ }, "node_modules/detect-node": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "license": "MIT" }, "node_modules/diff": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } }, + "node_modules/diff3": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz", + "integrity": "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==", + "license": "MIT" + }, "node_modules/dir-glob": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "license": "MIT", "dependencies": { @@ -8748,8 +8509,6 @@ }, "node_modules/duplexer2": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", "dev": true, "license": "BSD-3-Clause", "peer": true, @@ -8759,16 +8518,12 @@ }, "node_modules/duplexer2/node_modules/isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, "license": "MIT", "peer": true }, "node_modules/duplexer2/node_modules/readable-stream": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", "peer": true, @@ -8784,16 +8539,12 @@ }, "node_modules/duplexer2/node_modules/safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "license": "MIT", "peer": true }, "node_modules/duplexer2/node_modules/string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", "peer": true, @@ -8843,8 +8594,6 @@ }, "node_modules/emojilib": { "version": "2.4.0", - "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", - "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", "dev": true, "license": "MIT", "peer": true @@ -8860,8 +8609,6 @@ }, "node_modules/end-of-stream": { "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -8906,8 +8653,6 @@ }, "node_modules/env-ci": { "version": "11.2.0", - "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.2.0.tgz", - "integrity": "sha512-D5kWfzkmaOQDioPmiviWAVtKmpPT4/iJmMVQxWxMPJTFyTkdc5JQUfc5iXEeWxcOdsYTKSAiA/Age4NUOqKsRA==", "dev": true, "license": "MIT", "peer": true, @@ -8921,8 +8666,6 @@ }, "node_modules/env-ci/node_modules/execa": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "license": "MIT", "peer": true, @@ -8946,8 +8689,6 @@ }, "node_modules/env-ci/node_modules/get-stream": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, "license": "MIT", "peer": true, @@ -8960,8 +8701,6 @@ }, "node_modules/env-ci/node_modules/human-signals": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -8971,8 +8710,6 @@ }, "node_modules/env-ci/node_modules/is-stream": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, "license": "MIT", "peer": true, @@ -8985,8 +8722,6 @@ }, "node_modules/env-ci/node_modules/mimic-fn": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true, "license": "MIT", "peer": true, @@ -8999,8 +8734,6 @@ }, "node_modules/env-ci/node_modules/npm-run-path": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, "license": "MIT", "peer": true, @@ -9016,8 +8749,6 @@ }, "node_modules/env-ci/node_modules/onetime": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, "license": "MIT", "peer": true, @@ -9033,8 +8764,6 @@ }, "node_modules/env-ci/node_modules/path-key": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", "peer": true, @@ -9047,8 +8776,6 @@ }, "node_modules/env-ci/node_modules/strip-final-newline": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, "license": "MIT", "peer": true, @@ -9061,8 +8788,6 @@ }, "node_modules/env-paths": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "license": "MIT", "peer": true, @@ -9072,8 +8797,6 @@ }, "node_modules/environment": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, "license": "MIT", "peer": true, @@ -9482,8 +9205,6 @@ }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -9526,8 +9247,6 @@ }, "node_modules/eslint-scope": { "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -9563,8 +9282,6 @@ }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -9574,8 +9291,6 @@ }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -9623,8 +9338,6 @@ }, "node_modules/espree": { "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -9641,8 +9354,6 @@ }, "node_modules/espree/node_modules/eslint-visitor-keys": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -9776,8 +9487,6 @@ }, "node_modules/execa/node_modules/is-stream": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "license": "MIT", "engines": { "node": ">=18" @@ -9849,8 +9558,6 @@ }, "node_modules/extract-zip": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "license": "BSD-2-Clause", "dependencies": { "debug": "^4.1.1", @@ -9869,8 +9576,6 @@ }, "node_modules/extract-zip/node_modules/get-stream": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "license": "MIT", "dependencies": { "pump": "^3.0.0" @@ -9884,8 +9589,6 @@ }, "node_modules/fast-content-type-parse": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", "dev": true, "funding": [ { @@ -9922,8 +9625,6 @@ }, "node_modules/fast-levenshtein": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", - "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", "license": "MIT", "dependencies": { "fastest-levenshtein": "^1.0.7" @@ -9973,8 +9674,6 @@ }, "node_modules/fd-slicer": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "license": "MIT", "dependencies": { "pend": "~1.2.0" @@ -9982,8 +9681,6 @@ }, "node_modules/figures": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "license": "MIT", "dependencies": { "is-unicode-supported": "^2.0.0" @@ -10126,8 +9823,6 @@ }, "node_modules/find-up-simple": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", - "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", "dev": true, "license": "MIT", "peer": true, @@ -10140,8 +9835,6 @@ }, "node_modules/find-versions": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-6.0.0.tgz", - "integrity": "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==", "dev": true, "license": "MIT", "peer": true, @@ -10221,7 +9914,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.2.7" @@ -10295,8 +9987,6 @@ }, "node_modules/from2": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", "dev": true, "license": "MIT", "peer": true, @@ -10307,16 +9997,12 @@ }, "node_modules/from2/node_modules/isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, "license": "MIT", "peer": true }, "node_modules/from2/node_modules/readable-stream": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", "peer": true, @@ -10332,16 +10018,12 @@ }, "node_modules/from2/node_modules/safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "license": "MIT", "peer": true }, "node_modules/from2/node_modules/string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", "peer": true, @@ -10409,8 +10091,6 @@ }, "node_modules/function-timeout": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", - "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", "dev": true, "license": "MIT", "peer": true, @@ -10534,8 +10214,6 @@ }, "node_modules/get-port": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", - "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", "license": "MIT", "engines": { "node": ">=16" @@ -10641,8 +10319,6 @@ }, "node_modules/git-log-parser": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.1.tgz", - "integrity": "sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==", "dev": true, "license": "MIT", "peer": true, @@ -10657,16 +10333,12 @@ }, "node_modules/git-log-parser/node_modules/isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, "license": "MIT", "peer": true }, "node_modules/git-log-parser/node_modules/readable-stream": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", "peer": true, @@ -10682,16 +10354,12 @@ }, "node_modules/git-log-parser/node_modules/safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "license": "MIT", "peer": true }, "node_modules/git-log-parser/node_modules/split2": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", - "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", "dev": true, "license": "ISC", "peer": true, @@ -10701,8 +10369,6 @@ }, "node_modules/git-log-parser/node_modules/string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", "peer": true, @@ -10712,8 +10378,6 @@ }, "node_modules/git-log-parser/node_modules/through2": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "license": "MIT", "peer": true, @@ -10915,8 +10579,6 @@ }, "node_modules/handlebars": { "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "license": "MIT", "peer": true, @@ -10972,7 +10634,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -11086,8 +10747,6 @@ }, "node_modules/highlight.js": { "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "dev": true, "license": "BSD-3-Clause", "peer": true, @@ -11190,8 +10849,6 @@ }, "node_modules/http-proxy-agent": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "license": "MIT", "dependencies": { "agent-base": "^7.1.0", @@ -11230,8 +10887,6 @@ }, "node_modules/https-proxy-agent": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", "dependencies": { "agent-base": "^7.1.2", @@ -11243,8 +10898,6 @@ }, "node_modules/human-signals": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", - "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -11337,8 +10990,6 @@ }, "node_modules/import-from-esm": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-2.0.0.tgz", - "integrity": "sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==", "dev": true, "license": "MIT", "peer": true, @@ -11352,8 +11003,6 @@ }, "node_modules/import-meta-resolve": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", "dev": true, "license": "MIT", "peer": true, @@ -11425,8 +11074,6 @@ }, "node_modules/into-stream": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", - "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", "dev": true, "license": "MIT", "peer": true, @@ -11544,7 +11191,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -11687,8 +11333,6 @@ }, "node_modules/is-in-ci": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", - "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", "license": "MIT", "bin": { "is-in-ci": "cli.js" @@ -11715,8 +11359,6 @@ }, "node_modules/is-negative-zero": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "license": "MIT", "engines": { @@ -11728,8 +11370,6 @@ }, "node_modules/is-node-process": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", - "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", "dev": true, "license": "MIT" }, @@ -11923,7 +11563,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" @@ -12026,7 +11665,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -12035,10 +11673,51 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/isomorphic-git": { + "version": "1.37.2", + "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.37.2.tgz", + "integrity": "sha512-HCQBBKmXIMPdHgYGstSBNp6MNmVcMQBbUqJF8xfywFmlpNseO4KKex59YlXqNxhRxmv3fUZwvNWvMyOdc1VvhA==", + "license": "MIT", + "dependencies": { + "async-lock": "^1.4.1", + "clean-git-ref": "^2.0.1", + "crc-32": "^1.2.0", + "diff3": "0.0.3", + "ignore": "^5.1.4", + "minimisted": "^2.0.0", + "pako": "^1.0.10", + "pify": "^4.0.1", + "readable-stream": "^4.0.0", + "sha.js": "^2.4.12", + "simple-get": "^4.0.1" + }, + "bin": { + "isogit": "cli.cjs" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/isomorphic-git/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/isomorphic-git/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/issue-parser": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", - "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", "dev": true, "license": "MIT", "peer": true, @@ -12139,8 +11818,6 @@ }, "node_modules/istanbul-lib-processinfo/node_modules/uuid": { "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true, "license": "MIT", "bin": { @@ -12267,8 +11944,6 @@ }, "node_modules/java-properties": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", - "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", "dev": true, "license": "MIT", "peer": true, @@ -12346,8 +12021,6 @@ }, "node_modules/json-stringify-safe": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true, "license": "ISC" }, @@ -12543,8 +12216,6 @@ }, "node_modules/lint-staged/node_modules/execa": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", "dependencies": { @@ -12580,8 +12251,6 @@ }, "node_modules/lint-staged/node_modules/human-signals": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -12590,8 +12259,6 @@ }, "node_modules/lint-staged/node_modules/npm-run-path": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { @@ -12610,8 +12277,6 @@ }, "node_modules/lint-staged/node_modules/strip-final-newline": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { @@ -12679,8 +12344,6 @@ }, "node_modules/load-json-file": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", "dev": true, "license": "MIT", "peer": true, @@ -12696,8 +12359,6 @@ }, "node_modules/load-json-file/node_modules/parse-json": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, "license": "MIT", "peer": true, @@ -12711,8 +12372,6 @@ }, "node_modules/load-json-file/node_modules/strip-bom": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "license": "MIT", "peer": true, @@ -12752,30 +12411,22 @@ }, "node_modules/lodash.capitalize": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", - "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", "dev": true, "license": "MIT", "peer": true }, "node_modules/lodash.defaultsdeep": { "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", - "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==", "license": "MIT" }, "node_modules/lodash.escaperegexp": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", "dev": true, "license": "MIT", "peer": true }, "node_modules/lodash.flatmap": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz", - "integrity": "sha512-/OcpcAGWlrZyoHGeHh3cAoa6nGdX6QYtmzNP84Jqol6UEQQ2gIaU3H+0eICcjcKGl0/XF8LWOujNn9lffsnaOg==", "license": "MIT" }, "node_modules/lodash.flattendeep": { @@ -12795,16 +12446,12 @@ }, "node_modules/lodash.isplainobject": { "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true, "license": "MIT", "peer": true }, "node_modules/lodash.isstring": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "dev": true, "license": "MIT", "peer": true @@ -12818,8 +12465,6 @@ }, "node_modules/lodash.uniqby": { "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", - "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", "dev": true, "license": "MIT", "peer": true @@ -13056,8 +12701,6 @@ }, "node_modules/marked": { "version": "15.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", - "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", "peer": true, @@ -13070,8 +12713,6 @@ }, "node_modules/marked-terminal": { "version": "7.3.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", - "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", "dev": true, "license": "MIT", "peer": true, @@ -13110,8 +12751,6 @@ }, "node_modules/marked-terminal/node_modules/chalk": { "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, "license": "MIT", "peer": true, @@ -13190,8 +12829,6 @@ }, "node_modules/merge-stream": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true, "license": "MIT" }, @@ -13308,7 +12945,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -13339,6 +12975,15 @@ "node": ">=0.10.0" } }, + "node_modules/minimisted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minimisted/-/minimisted-2.0.1.tgz", + "integrity": "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5" + } + }, "node_modules/minipass": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", @@ -13404,8 +13049,6 @@ }, "node_modules/mocha/node_modules/chokidar": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", "dependencies": { @@ -13436,8 +13079,6 @@ }, "node_modules/mocha/node_modules/readdirp": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, "license": "MIT", "engines": { @@ -13460,8 +13101,6 @@ }, "node_modules/mock-fs": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.5.0.tgz", - "integrity": "sha512-d/P1M/RacgM3dB0sJ8rjeRNXxtapkPCUnMGmIN0ixJ16F/E4GUZCvWcSGfWGz8eaXYvn1s9baUwNjI4LOPEjiA==", "dev": true, "license": "MIT", "engines": { @@ -13486,8 +13125,6 @@ }, "node_modules/mz": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, "license": "MIT", "peer": true, @@ -13515,24 +13152,18 @@ }, "node_modules/neo-async": { "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, "license": "MIT", "peer": true }, "node_modules/nerf-dart": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", - "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", "dev": true, "license": "MIT", "peer": true }, "node_modules/neverthrow": { "version": "8.2.0", - "resolved": "https://registry.npmjs.org/neverthrow/-/neverthrow-8.2.0.tgz", - "integrity": "sha512-kOCT/1MCPAxY5iUV3wytNFUMUolzuwd/VF/1KCx7kf6CutrOsTie+84zTGTpgQycjvfLdBBdvBvFLqFD2c0wkQ==", "license": "MIT", "engines": { "node": ">=18" @@ -13569,8 +13200,6 @@ }, "node_modules/node-emoji": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", - "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", "dev": true, "license": "MIT", "peer": true, @@ -13586,8 +13215,6 @@ }, "node_modules/node-emoji/node_modules/@sindresorhus/is": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true, "license": "MIT", "peer": true, @@ -13820,8 +13447,6 @@ }, "node_modules/npm-run-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", - "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "license": "MIT", "dependencies": { "path-key": "^4.0.0", @@ -13836,8 +13461,6 @@ }, "node_modules/npm-run-path/node_modules/path-key": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "license": "MIT", "engines": { "node": ">=12" @@ -16742,8 +16365,6 @@ }, "node_modules/object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, "license": "MIT", "peer": true, @@ -16865,6 +16486,7 @@ "@oclif/plugin-warn-if-update-available": "^3.1.55", "ansis": "^3.16.0", "async-retry": "^1.3.3", + "chalk": "^4", "change-case": "^4", "debug": "^4.4.0", "ejs": "^3.1.10", @@ -16983,8 +16605,6 @@ }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { "wrappy": "1" @@ -17056,8 +16676,6 @@ }, "node_modules/outvariant": { "version": "1.4.3", - "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", - "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", "dev": true, "license": "MIT" }, @@ -17091,8 +16709,6 @@ }, "node_modules/p-each-series": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", - "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==", "dev": true, "license": "MIT", "peer": true, @@ -17122,8 +16738,6 @@ }, "node_modules/p-filter": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz", - "integrity": "sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==", "dev": true, "license": "MIT", "peer": true, @@ -17153,8 +16767,6 @@ }, "node_modules/p-is-promise": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", - "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", "dev": true, "license": "MIT", "peer": true, @@ -17212,8 +16824,6 @@ }, "node_modules/p-reduce": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz", - "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==", "dev": true, "license": "MIT", "engines": { @@ -17266,6 +16876,12 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -17311,8 +16927,6 @@ }, "node_modules/parse-ms": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", - "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", "license": "MIT", "engines": { "node": ">=18" @@ -17323,16 +16937,12 @@ }, "node_modules/parse5": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "dev": true, "license": "MIT", "peer": true }, "node_modules/parse5-htmlparser2-tree-adapter": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", "dev": true, "license": "MIT", "peer": true, @@ -17342,8 +16952,6 @@ }, "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true, "license": "MIT", "peer": true @@ -17455,8 +17063,6 @@ }, "node_modules/pend": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "license": "MIT" }, "node_modules/picocolors": { @@ -17479,8 +17085,6 @@ }, "node_modules/pify": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, "license": "MIT", "peer": true, @@ -17490,8 +17094,6 @@ }, "node_modules/pkg-conf": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", - "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", "dev": true, "license": "MIT", "peer": true, @@ -17505,8 +17107,6 @@ }, "node_modules/pkg-conf/node_modules/find-up": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", "dev": true, "license": "MIT", "peer": true, @@ -17519,8 +17119,6 @@ }, "node_modules/pkg-conf/node_modules/locate-path": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", "dev": true, "license": "MIT", "peer": true, @@ -17534,8 +17132,6 @@ }, "node_modules/pkg-conf/node_modules/p-limit": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "license": "MIT", "peer": true, @@ -17548,8 +17144,6 @@ }, "node_modules/pkg-conf/node_modules/p-locate": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", "dev": true, "license": "MIT", "peer": true, @@ -17562,8 +17156,6 @@ }, "node_modules/pkg-conf/node_modules/p-try": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", "dev": true, "license": "MIT", "peer": true, @@ -17573,8 +17165,6 @@ }, "node_modules/pkg-conf/node_modules/path-exists": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "dev": true, "license": "MIT", "peer": true, @@ -17665,7 +17255,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -17741,8 +17330,6 @@ }, "node_modules/propagate": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", "dev": true, "license": "MIT", "engines": { @@ -17867,8 +17454,6 @@ }, "node_modules/rc": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "peer": true, @@ -17884,8 +17469,6 @@ }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, "license": "MIT", "peer": true, @@ -17895,8 +17478,6 @@ }, "node_modules/read-package-up": { "version": "11.0.0", - "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", - "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", "dev": true, "license": "MIT", "peer": true, @@ -17914,8 +17495,6 @@ }, "node_modules/read-package-up/node_modules/hosted-git-info": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, "license": "ISC", "peer": true, @@ -17928,16 +17507,12 @@ }, "node_modules/read-package-up/node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC", "peer": true }, "node_modules/read-package-up/node_modules/normalize-package-data": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -17952,8 +17527,6 @@ }, "node_modules/read-package-up/node_modules/parse-json": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", - "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "dev": true, "license": "MIT", "peer": true, @@ -17971,8 +17544,6 @@ }, "node_modules/read-package-up/node_modules/read-pkg": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", - "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", "dev": true, "license": "MIT", "peer": true, @@ -17992,8 +17563,6 @@ }, "node_modules/read-package-up/node_modules/type-fest": { "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, "license": "(MIT OR CC0-1.0)", "peer": true, @@ -18006,8 +17575,6 @@ }, "node_modules/read-package-up/node_modules/unicorn-magic": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", "dev": true, "license": "MIT", "peer": true, @@ -18376,8 +17943,6 @@ }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", "funding": { @@ -18596,8 +18161,6 @@ }, "node_modules/semantic-release/node_modules/@semantic-release/error": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", - "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, "license": "MIT", "peer": true, @@ -18607,8 +18170,6 @@ }, "node_modules/semantic-release/node_modules/aggregate-error": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", - "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, "license": "MIT", "peer": true, @@ -18670,8 +18231,6 @@ }, "node_modules/semantic-release/node_modules/escape-string-regexp": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "license": "MIT", "peer": true, @@ -18698,8 +18257,6 @@ }, "node_modules/semantic-release/node_modules/hosted-git-info": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", "dev": true, "license": "ISC", "peer": true, @@ -18712,8 +18269,6 @@ }, "node_modules/semantic-release/node_modules/indent-string": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "license": "MIT", "peer": true, @@ -18726,16 +18281,12 @@ }, "node_modules/semantic-release/node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC", "peer": true }, "node_modules/semantic-release/node_modules/p-reduce": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz", - "integrity": "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==", "dev": true, "license": "MIT", "peer": true, @@ -18789,8 +18340,6 @@ }, "node_modules/semver-regex": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", - "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", "dev": true, "license": "MIT", "peer": true, @@ -18900,7 +18449,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -18951,6 +18499,26 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -19058,8 +18626,6 @@ }, "node_modules/signale": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", - "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", "dev": true, "license": "MIT", "peer": true, @@ -19074,8 +18640,6 @@ }, "node_modules/signale/node_modules/ansi-styles": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "license": "MIT", "peer": true, @@ -19088,8 +18652,6 @@ }, "node_modules/signale/node_modules/chalk": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "license": "MIT", "peer": true, @@ -19104,8 +18666,6 @@ }, "node_modules/signale/node_modules/color-convert": { "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "license": "MIT", "peer": true, @@ -19115,16 +18675,12 @@ }, "node_modules/signale/node_modules/color-name": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true, "license": "MIT", "peer": true }, "node_modules/signale/node_modules/escape-string-regexp": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, "license": "MIT", "peer": true, @@ -19134,8 +18690,6 @@ }, "node_modules/signale/node_modules/figures": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", "dev": true, "license": "MIT", "peer": true, @@ -19148,8 +18702,6 @@ }, "node_modules/signale/node_modules/has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "license": "MIT", "peer": true, @@ -19159,8 +18711,6 @@ }, "node_modules/signale/node_modules/supports-color": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "license": "MIT", "peer": true, @@ -19171,6 +18721,51 @@ "node": ">=4" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sinon": { "version": "21.0.2", "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.2.tgz", @@ -19201,8 +18796,6 @@ }, "node_modules/sinon/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -19214,14 +18807,10 @@ }, "node_modules/sisteransi": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, "node_modules/skin-tone": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", - "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", "dev": true, "license": "MIT", "peer": true, @@ -19321,8 +18910,6 @@ }, "node_modules/spawn-error-forwarder": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", - "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==", "dev": true, "license": "MIT", "peer": true @@ -19445,8 +19032,6 @@ }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", "dev": true, "license": "MIT", "dependencies": { @@ -19459,8 +19044,6 @@ }, "node_modules/stream-combiner2": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", "dev": true, "license": "MIT", "peer": true, @@ -19471,16 +19054,12 @@ }, "node_modules/stream-combiner2/node_modules/isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, "license": "MIT", "peer": true }, "node_modules/stream-combiner2/node_modules/readable-stream": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", "peer": true, @@ -19496,16 +19075,12 @@ }, "node_modules/stream-combiner2/node_modules/safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "license": "MIT", "peer": true }, "node_modules/stream-combiner2/node_modules/string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", "peer": true, @@ -19526,8 +19101,6 @@ }, "node_modules/strict-event-emitter": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", - "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", "dev": true, "license": "MIT" }, @@ -19754,8 +19327,6 @@ }, "node_modules/strip-final-newline": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "license": "MIT", "engines": { "node": ">=18" @@ -19845,8 +19416,6 @@ }, "node_modules/supports-hyperlinks": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", - "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", "dev": true, "license": "MIT", "peer": true, @@ -19863,8 +19432,6 @@ }, "node_modules/supports-hyperlinks/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "peer": true, @@ -19911,8 +19478,6 @@ }, "node_modules/temp-dir": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", - "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", "dev": true, "license": "MIT", "peer": true, @@ -19942,8 +19507,6 @@ }, "node_modules/tempy/node_modules/is-stream": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, "license": "MIT", "peer": true, @@ -19956,8 +19519,6 @@ }, "node_modules/tempy/node_modules/type-fest": { "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, "license": "(MIT OR CC0-1.0)", "peer": true, @@ -19992,8 +19553,6 @@ }, "node_modules/test-exclude/node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -20035,8 +19594,6 @@ }, "node_modules/thenify": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "dev": true, "license": "MIT", "peer": true, @@ -20046,8 +19603,6 @@ }, "node_modules/thenify-all": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "dev": true, "license": "MIT", "peer": true, @@ -20092,8 +19647,6 @@ }, "node_modules/time-span": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", - "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", "dev": true, "license": "MIT", "peer": true, @@ -20116,8 +19669,6 @@ }, "node_modules/tiny-warning": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "license": "MIT" }, "node_modules/tinyglobby": { @@ -20167,8 +19718,6 @@ }, "node_modules/tmp": { "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "license": "MIT", "engines": { "node": ">=14.14" @@ -20176,13 +19725,25 @@ }, "node_modules/tmp-promise": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", - "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", "license": "MIT", "dependencies": { "tmp": "^0.2.0" } }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -20206,8 +19767,6 @@ }, "node_modules/traverse": { "version": "0.6.8", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", - "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", "dev": true, "license": "MIT", "peer": true, @@ -20243,8 +19802,6 @@ }, "node_modules/ts-node": { "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", "dependencies": { @@ -20333,8 +19890,6 @@ }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/tsx": { @@ -20422,7 +19977,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -20522,8 +20076,6 @@ }, "node_modules/uglify-js": { "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, "license": "BSD-2-Clause", "optional": true, @@ -20563,8 +20115,6 @@ }, "node_modules/unicode-emoji-modifier-base": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", - "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", "dev": true, "license": "MIT", "peer": true, @@ -20574,8 +20124,6 @@ }, "node_modules/unicorn-magic": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "license": "MIT", "engines": { "node": ">=18" @@ -20586,8 +20134,6 @@ }, "node_modules/unique-string": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", - "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", "dev": true, "license": "MIT", "peer": true, @@ -20603,8 +20149,6 @@ }, "node_modules/universal-user-agent": { "version": "7.0.3", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", - "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", "dev": true, "license": "ISC", "peer": true @@ -20690,8 +20234,6 @@ }, "node_modules/url-join": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", - "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", "dev": true, "license": "MIT", "peer": true, @@ -20716,8 +20258,6 @@ }, "node_modules/uuid": { "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -20729,8 +20269,6 @@ }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true, "license": "MIT" }, @@ -20865,7 +20403,6 @@ "version": "1.1.20", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", - "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -20997,8 +20534,6 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, "node_modules/write-file-atomic": { @@ -21044,8 +20579,6 @@ }, "node_modules/xtend": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true, "license": "MIT", "peer": true, @@ -21178,8 +20711,6 @@ }, "node_modules/yauzl": { "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", @@ -21188,8 +20719,6 @@ }, "node_modules/yauzl/node_modules/buffer-crc32": { "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "license": "MIT", "engines": { "node": "*" diff --git a/package.json b/package.json index dee9603b..4798232c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@apimatic/cli", "description": "The official CLI for APIMatic.", - "version": "1.1.0-beta.10", + "version": "1.1.0-beta.10-test", "author": "APIMatic", "bin": { "apimatic": "./bin/run.js" @@ -20,7 +20,8 @@ "./bin", "./lib", "/npm-shrinkwrap.json", - "/oclif.manifest.json" + "/oclif.manifest.json", + "/apimatic-sdk-0.2.0-alpha.8-test.tgz" ], "repository": "git://github.com/apimatic/apimatic-cli.git", "homepage": "https://docs.apimatic.io/apimatic-cli/intro-and-install/", @@ -47,7 +48,7 @@ "test": "tsx node_modules/mocha/bin/_mocha --forbid-only \"test/**/*.test.ts\" --timeout 99999" }, "dependencies": { - "@apimatic/sdk": "^0.2.0-alpha.7", + "@apimatic/sdk": "file:./apimatic-sdk-0.2.0-alpha.8-test.tgz", "@clack/prompts": "1.0.0-alpha.1", "@oclif/core": "^4.2.8", "@oclif/plugin-autocomplete": "^3.2.24", @@ -65,6 +66,7 @@ "fs-extra": "^11.3.0", "get-port": "^7.1.0", "is-in-ci": "^2.0.0", + "isomorphic-git": "^1.37.2", "livereload": "^0.9.3", "neverthrow": "^8.2.0", "open": "^8.4.0", @@ -160,4 +162,4 @@ "glob": "^10.4.5", "q": "npm:promise@^8.3.0" } -} +} \ No newline at end of file diff --git a/src/actions/portal/generate.ts b/src/actions/portal/generate.ts index 3bffe910..c87fd151 100644 --- a/src/actions/portal/generate.ts +++ b/src/actions/portal/generate.ts @@ -59,9 +59,13 @@ export class GenerateAction { if (response.isErr()) { const error = response.error; if (error instanceof ServiceError) { - this.prompts.portalGenerationServiceError(error); - } - else if (typeof error === "string") { + const sdkMergeFailedErrors = error.getError("sdkMergeFailed"); + if (sdkMergeFailedErrors) { + this.prompts.portalGenerationSdkMergeFailed(sdkMergeFailedErrors); + } else { + this.prompts.portalGenerationError(error.errorMessage); + } + } else if (typeof error === "string") { this.prompts.portalGenerationError(error); } else { const errorZipPath = await tempContext.save(error); @@ -82,4 +86,4 @@ export class GenerateAction { return ActionResult.success(); }); }; -} +} \ No newline at end of file diff --git a/src/actions/sdk/generate.ts b/src/actions/sdk/generate.ts index 02a64ece..396bb9f0 100644 --- a/src/actions/sdk/generate.ts +++ b/src/actions/sdk/generate.ts @@ -3,12 +3,12 @@ import { DirectoryPath } from "../../types/file/directoryPath.js"; import { ActionResult } from "../action-result.js"; import { withDirPath } from "../../infrastructure/tmp-extensions.js"; import { SdkContext } from "../../types/sdk-context.js"; -import { VersionedBuildContext } from "../../types/versioned-build-context.js"; import { SdkGeneratePrompts } from "../../prompts/sdk/generate.js"; import { CommandMetadata } from "../../types/common/command-metadata.js"; import { TempContext } from "../../types/temp-context.js"; import { Language } from "../../types/sdk/generate.js"; -import { SpecContext } from "../../types/spec-context.js"; +import { MergeSourceTreeAction } from "./merge-source-tree.js"; +import { BuildContext } from "../../types/build-context.js"; export class GenerateAction { private readonly prompts: SdkGeneratePrompts = new SdkGeneratePrompts(); @@ -25,79 +25,113 @@ export class GenerateAction { public readonly execute = async ( buildDirectory: DirectoryPath, - sdkDirectory: DirectoryPath, + destinationSdkDirectory: DirectoryPath, language: Language, force: boolean, zipSdk: boolean, + skipChanges: boolean, + trackChanges: boolean, apiVersion?: string - ): Promise => { - if (buildDirectory.isEqual(sdkDirectory)) { + ): Promise> => { + if (buildDirectory.isEqual(destinationSdkDirectory)) { this.prompts.sameBuildAndSdkDir(buildDirectory); return ActionResult.failed(); } - const versionedBuildContext = new VersionedBuildContext(buildDirectory); - const versionedBuildResult = await versionedBuildContext.validate(); - if (versionedBuildResult.isValid) { - if (versionedBuildResult.versions.length === 0) { - this.prompts.versionedBuildEmpty(versionedBuildResult.versionsDirectory); + const rootBuildContext = new BuildContext(buildDirectory); + if (!(await rootBuildContext.validate())) { + this.prompts.srcDirectoryEmpty(buildDirectory); + return ActionResult.failed(); + } + + const versionedContextGetter = async () => { + if (!await rootBuildContext.isVersionedBuild()) { + if (apiVersion) this.prompts.apiVersionOnlyApplicableWithVersionedBuild(); + return { version: undefined, buildContext: rootBuildContext }; + } + + const versionedBuildDirectory = await rootBuildContext.getVersionedBuildDirectory(); + if (!versionedBuildDirectory) { + this.prompts.invalidVersionedDocsDirectory(buildDirectory); return ActionResult.failed(); } - let version: string; - if (apiVersion) { - if (!versionedBuildResult.versions.includes(apiVersion)) { - this.prompts.versionNotFound(); - return ActionResult.failed(); - } - version = apiVersion; - } else if (versionedBuildResult.versions.length === 1) { - version = versionedBuildResult.versions[0]; - } else { - const selectedVersion = await this.prompts.selectVersion(versionedBuildResult.versions); - if (!selectedVersion) { - return ActionResult.cancelled(); - } - version = selectedVersion; + const singleVersionedBuildDirectory = await rootBuildContext.getSingleVersionedBuildDirectory(); + if (!apiVersion && singleVersionedBuildDirectory) { + return { + version: singleVersionedBuildDirectory.leafName(), + buildContext: new BuildContext(singleVersionedBuildDirectory) + }; + } + + const selectedVersionedBuildDirectory = await rootBuildContext.getSelectedVersionedBuildDirectory( + apiVersion ? async () => apiVersion : this.prompts.selectVersion + ); + if (!selectedVersionedBuildDirectory) { + this.prompts.versionNotFound(); + return ActionResult.failed(); } - buildDirectory = versionedBuildResult.versionsDirectory.join(version); - sdkDirectory = sdkDirectory.join(version); + return { + version: selectedVersionedBuildDirectory.leafName(), + buildContext: new BuildContext(selectedVersionedBuildDirectory) + }; + }; + + const versionedContext = await versionedContextGetter(); + if (versionedContext instanceof ActionResult) { + return versionedContext; } - const specDirectory = buildDirectory.join("spec"); - const specContext = new SpecContext(specDirectory); - if (!(await specContext.validate())) { - this.prompts.specDirectoryEmpty(specDirectory); + const { version, buildContext } = versionedContext; + + if (!(await buildContext.getSpecContext().validate())) { + this.prompts.specDirectoryEmpty(buildDirectory); return ActionResult.failed(); } - const sdkContext = new SdkContext(sdkDirectory, language); - if (!force && (await sdkContext.exists()) && !(await this.prompts.overwriteSdk(sdkContext.sdkLanguageDirectory))) { + const hasSdkSourceTree = await buildContext.hasSdkSourceTree(language); + const sdkContext = new SdkContext(language, destinationSdkDirectory, skipChanges && hasSdkSourceTree, version); + if (!force && await sdkContext.exists() && !(await this.prompts.overwriteSdk(destinationSdkDirectory))) { this.prompts.destinationDirNotEmpty(); return ActionResult.cancelled(); } return await withDirPath(async (tempDirectory) => { const tempContext = new TempContext(tempDirectory); - const buildZipPath = await tempContext.zip(buildDirectory); + const buildZipPath = await tempContext.zip(buildContext.getBuildDirectory()); const response = await this.prompts.generateSDK( this.portalService.generateSdk(buildZipPath, language, this.configDir, this.commandMetadata, this.authKey) ); - // TODO: this should be service error if (response.isErr()) { this.prompts.sdkGenerationServiceError(response.error); return ActionResult.failed(); } - const tempSdkFilePath = await tempContext.save(response.value); - const sdkLanguageDirectory = await sdkContext.save(tempSdkFilePath, zipSdk); + const responseSdkZipPath = await tempContext.save(response.value.sdk); + const tempSdk = await sdkContext.loadSdkInTempDirectory(tempDirectory, responseSdkZipPath); + + if (!trackChanges && !hasSdkSourceTree) { + this.prompts.sdkGenerated(await sdkContext.save(tempSdk, zipSdk)); + return ActionResult.success(); + } + + const sdkSourceTreeTempFilePath = await tempContext.save(response.value.sdkSourceTree); - this.prompts.sdkGenerated(sdkLanguageDirectory); + const tempSdkWithSourceTree = await sdkContext.loadSdkWithSourceTreeInTempDirectory( + tempDirectory, + responseSdkZipPath, + sdkSourceTreeTempFilePath + ); + const destinationSourceTreePath = buildContext.getSdkSourceTree(language); - return ActionResult.success(); + const mergeSourceTree = new MergeSourceTreeAction(); + return await mergeSourceTree.execute( + tempSdkWithSourceTree, tempSdk, destinationSourceTreePath, trackChanges, skipChanges, hasSdkSourceTree, + language, destinationSdkDirectory, version, zipSdk + ); }); }; } diff --git a/src/actions/sdk/merge-source-tree.ts b/src/actions/sdk/merge-source-tree.ts new file mode 100644 index 00000000..ca63b26e --- /dev/null +++ b/src/actions/sdk/merge-source-tree.ts @@ -0,0 +1,95 @@ +import { DirectoryPath } from "../../types/file/directoryPath.js"; +import { FilePath } from "../../types/file/filePath.js"; +import { LauncherService } from "../../infrastructure/launcher-service.js"; +import { MergeSourceTreePrompts } from "../../prompts/sdk/merge-source-tree.js"; +import { Language } from "../../types/sdk/generate.js"; +import { ActionResult } from "../action-result.js"; +import isInCi from "is-in-ci"; +import { MergeSourceTreeContext } from "../../types/merge-source-tree-context.js"; +import { SdkContext } from "../../types/sdk-context.js"; + +export class MergeSourceTreeAction { + private readonly prompts = new MergeSourceTreePrompts(); + private readonly launcherService = new LauncherService(); + + public readonly execute = async ( + sdkWithSourceTree: DirectoryPath, + sdkWithoutSourceTree: DirectoryPath, + destinationSourceTreePath: FilePath, + trackChanges: boolean, + skipChanges: boolean, + hasSdkSourceTree: boolean, + language: Language, + outputSdkDirectory: DirectoryPath, + version: string | undefined, + zipSdk: boolean + ): Promise> => { + const mergeSourceTreeContext = new MergeSourceTreeContext( + sdkWithSourceTree, sdkWithoutSourceTree, destinationSourceTreePath, + trackChanges, skipChanges, hasSdkSourceTree + ); + const sdkContext = new SdkContext(language, outputSdkDirectory, skipChanges && hasSdkSourceTree, version); + const saveSdk = async () => await sdkContext.save(sdkWithoutSourceTree, zipSdk); + + const { hasSkippedChangesEnabled, hasSkippedCustomizations } = await mergeSourceTreeContext.saveSkippingChanges(); + if (hasSkippedCustomizations) { + this.prompts.successfullySkippedChanges(language); + this.prompts.sdkGenerated(await saveSdk()); + return ActionResult.success(); + } + if (hasSkippedChangesEnabled) { + this.prompts.sdkGenerated(await saveSdk()); + return ActionResult.success(); + } + + const { hasSourceTreeTracked, hasSourceTreeAlreadyTracked, hasAppliedCustomizations } = await mergeSourceTreeContext.saveWithoutConflicts(); + if (hasAppliedCustomizations) { + this.prompts.successfullyAppliedChanges(language); + this.prompts.sdkGeneratedWithSourceTree(await saveSdk(), destinationSourceTreePath); + return ActionResult.success(); + } + if (hasSourceTreeAlreadyTracked) { + this.prompts.changeTrackingAlreadyEnabled(language); + this.prompts.sdkGeneratedWithSourceTree(await saveSdk(), destinationSourceTreePath); + return ActionResult.success(); + } + if (hasSourceTreeTracked) { + this.prompts.sdkGenerated(await saveSdk()); + this.prompts.changeTrackingEnabled(language, destinationSourceTreePath); + return ActionResult.success({sourceTreeTrackingInitiated: true, conflictsResolved: false}); + } + + let conflictedFilesDirectory = await mergeSourceTreeContext.getConflictedFilesDirectory(); + + this.prompts.conflictsDetected(language, conflictedFilesDirectory); + + if (isInCi) { + this.prompts.errorMergeConflicts(language); + return ActionResult.failed(); + } + + do { + this.prompts.openingDirectoryForConflictResolution(language); + + if (!await this.launcherService.openFolderInIdeWithWait(sdkWithSourceTree, conflictedFilesDirectory.getAllFiles()) + && !await this.prompts.waitForConflictsResolved(language, sdkWithSourceTree)) { + this.prompts.operationCancelled(); + return ActionResult.cancelled(); + } + + conflictedFilesDirectory = await mergeSourceTreeContext.getConflictedFilesDirectory(); + + if (!conflictedFilesDirectory.isEmpty()) { + this.prompts.conflictsStillPresent(conflictedFilesDirectory); + } + + } while (!conflictedFilesDirectory.isEmpty()); + + await mergeSourceTreeContext.saveWithResolvedConflicts(); + this.prompts.conflictsResolved(language); + this.prompts.sdkGeneratedWithSourceTree(await saveSdk(), destinationSourceTreePath); + + await mergeSourceTreeContext.cleanUp(() => this.prompts.directoryStillOpen(sdkWithSourceTree)); + return ActionResult.success({sourceTreeTrackingInitiated: false, conflictsResolved: true}); + }; +} diff --git a/src/actions/sdk/quickstart.ts b/src/actions/sdk/quickstart.ts index a2c1d0a1..d02ef1e6 100644 --- a/src/actions/sdk/quickstart.ts +++ b/src/actions/sdk/quickstart.ts @@ -179,7 +179,7 @@ export class SdkQuickstartAction { const sdkDirectory = inputDirectory.join('sdk'); const sdkGenerateAction = new GenerateAction(this.configDir, this.commandMetadata); - const result = await sdkGenerateAction.execute(sourceDirectory, sdkDirectory, language as Language, true, false); + const result = await sdkGenerateAction.execute(sourceDirectory, sdkDirectory, language as Language, true, false, false, false); if (result.isFailed()) { return ActionResult.failed(); } diff --git a/src/actions/sdk/save-changes.ts b/src/actions/sdk/save-changes.ts new file mode 100644 index 00000000..8028ca3b --- /dev/null +++ b/src/actions/sdk/save-changes.ts @@ -0,0 +1,123 @@ +import { DirectoryPath } from "../../types/file/directoryPath.js"; +import { ActionResult } from "../action-result.js"; +import { withDirPath } from "../../infrastructure/tmp-extensions.js"; +import { Language } from "../../types/sdk/generate.js"; +import { SaveChangesPrompts } from "../../prompts/sdk/save-changes.js"; +import { SaveChangesContext } from "../../types/save-changes-context.js"; +import { BuildContext } from "../../types/build-context.js"; +import { LauncherService } from "../../infrastructure/launcher-service.js"; + +export class SaveChangesAction { + private readonly prompts = new SaveChangesPrompts(); + private readonly launcherService = new LauncherService(); + + public readonly execute = async ( + workingDirectory: DirectoryPath, + buildDirectory: DirectoryPath, + sdkDirectory: DirectoryPath | undefined, + language: Language, + apiVersion?: string + ): Promise => { + const rootBuildContext = new BuildContext(buildDirectory); + if (!(await rootBuildContext.validate())) { + this.prompts.srcDirectoryEmpty(buildDirectory); + return ActionResult.failed(); + } + + const versionedContextGetter = async () => { + if (!await rootBuildContext.isVersionedBuild()) { + if (apiVersion) this.prompts.apiVersionOnlyApplicableWithVersionedBuild(); + return { version: undefined, buildContext: rootBuildContext }; + } + + const versionedBuildDirectory = await rootBuildContext.getVersionedBuildDirectory(); + if (!versionedBuildDirectory) { + this.prompts.invalidVersionedDocsDirectory(buildDirectory); + return ActionResult.failed(); + } + + const singleVersionedBuildDirectory = await rootBuildContext.getSingleVersionedBuildDirectory(); + if (!apiVersion && singleVersionedBuildDirectory) { + return { + version: singleVersionedBuildDirectory.leafName(), + buildContext: new BuildContext(singleVersionedBuildDirectory) + }; + } + + const selectedVersionedBuildDirectory = await rootBuildContext.getSelectedVersionedBuildDirectory( + apiVersion ? async () => apiVersion : this.prompts.selectVersion + ); + if (!selectedVersionedBuildDirectory) { + this.prompts.versionNotFound(); + return ActionResult.failed(); + } + + return { + version: selectedVersionedBuildDirectory.leafName(), + buildContext: new BuildContext(selectedVersionedBuildDirectory) + }; + }; + + const versionedContext = await versionedContextGetter(); + if (versionedContext instanceof ActionResult) { + return versionedContext; + } + + if (!(await versionedContext.buildContext.hasSdkSourceTree(language))) { + this.prompts.sdkSourceTreeNotFound(language); + return ActionResult.failed(); + } + + return await withDirPath(async (tempDirectory) => { + const sdkReviewDirectory = tempDirectory.join(language); + const sdkSourceTree = versionedContext.buildContext.getSdkSourceTree(language); + const saveChangesContext = new SaveChangesContext( + sdkSourceTree, + sdkReviewDirectory, + sdkDirectory, + workingDirectory, + language, + versionedContext.version + ); + + if (await saveChangesContext.isSdkInputDirectoryMissing(this.prompts.invalidSdkDirectory)) { + return ActionResult.failed(); + } + + const updatedFilesDirectory = await saveChangesContext.getChangesForReviewDirectory(); + if (updatedFilesDirectory.isEmpty()) { + this.prompts.noChangesDetected(); + return ActionResult.success(); + } + + this.prompts.modifiedFilesDetected(updatedFilesDirectory); + + if (!await this.prompts.confirmChanges()) { + await saveChangesContext.saveSourceTree(); + this.prompts.changesSaved(sdkSourceTree); + return ActionResult.success(); + } + + const nonDeletedFilesDirectory = await updatedFilesDirectory.mapFilesInDirectory(async (_, fileItem) => { + if (fileItem.description === "# Deleted") { + return undefined; + } + return fileItem; + }); + + this.prompts.openingDirectoryToReviewChanges(); + if (!await this.launcherService.openFolderInIdeWithWait(sdkReviewDirectory, nonDeletedFilesDirectory.getAllFiles()) + && !await this.prompts.reviewChangesManually(sdkReviewDirectory)) { + this.prompts.operationCancelled(); + return ActionResult.cancelled(); + } + + await saveChangesContext.saveSourceTree(); + this.prompts.changesSaved(sdkSourceTree); + + await saveChangesContext.cleanUpSdkReviewDirectory(() => this.prompts.directoryStillOpen(sdkReviewDirectory)); + + return ActionResult.success(); + }); + }; +} diff --git a/src/commands/portal/recipe/new.ts b/src/commands/portal/recipe/new.ts index 5cdbd803..f2eeeedc 100644 --- a/src/commands/portal/recipe/new.ts +++ b/src/commands/portal/recipe/new.ts @@ -47,8 +47,8 @@ ${format.link( const result = await action.execute(buildDirectory, name); outro(result); - result.mapAll( - () => {}, + await result.mapAll( + async () => {}, async () => { const telemetryService = new TelemetryService(new DirectoryPath(this.config.configDir)); await telemetryService.trackEvent( @@ -60,7 +60,7 @@ ${format.link( commandMetadata.shell ); }, - () => {} + async () => {} ); } } diff --git a/src/commands/portal/toc/new.ts b/src/commands/portal/toc/new.ts index 41a5ec74..bfb6c137 100644 --- a/src/commands/portal/toc/new.ts +++ b/src/commands/portal/toc/new.ts @@ -92,8 +92,8 @@ ${format.link( ); outro(result); - result.mapAll( - () => {}, + await result.mapAll( + async () => {}, async () => { const telemetryService = new TelemetryService(new DirectoryPath(this.config.configDir)); await telemetryService.trackEvent( @@ -110,7 +110,7 @@ ${format.link( commandMetadata.shell ); }, - () => {} + async () => {} ); } } diff --git a/src/commands/quickstart.ts b/src/commands/quickstart.ts index 443fe96e..8c90d176 100644 --- a/src/commands/quickstart.ts +++ b/src/commands/quickstart.ts @@ -33,8 +33,8 @@ export default class Quickstart extends Command { // TODO: Remove this, find a solution for tracking. await result.mapAll( async () => await telemetryService.trackEvent(new QuickstartCompletedEvent(), commandMetadata.shell), - () => new Promise(() => {}), - () => new Promise(() => {}) + async () => {}, + async () => {} ); } diff --git a/src/commands/sdk/generate.ts b/src/commands/sdk/generate.ts index 4ec08801..599c225d 100644 --- a/src/commands/sdk/generate.ts +++ b/src/commands/sdk/generate.ts @@ -5,6 +5,9 @@ import { GenerateAction } from "../../actions/sdk/generate.js"; import { Language } from "../../types/sdk/generate.js"; import { CommandMetadata } from "../../types/common/command-metadata.js"; import { format, intro, outro } from "../../prompts/format.js"; +import { SdkChangesTrackedEvent } from "../../types/events/sdk-changes-tracked.js"; +import { TelemetryService } from "../../infrastructure/services/telemetry-service.js"; +import { SdkConflictsResolvedEvent } from "../../types/events/sdk-conflicts-resolved.js"; export default class SdkGenerate extends Command { static readonly summary = "Generate an SDK for your API"; @@ -21,20 +24,28 @@ Supports multiple programming languages including Java, C#, Python, JavaScript, description: "Programming language for SDK generation", options: Object.values(Language).map((p) => p.valueOf()), }), - ...FlagsProvider.input, destination: Flags.string({ char: "d", - description: "Directory where the SDK will be generated" + description: "[default: /sdk/ | /sdk//] path where the SDK will be generated" + }), + "skip-changes": Flags.boolean({ + default: false, + description: "Do not apply the saved changes to the generated SDK" }), "api-version": Flags.string({ description: "Version of the API to use for SDK generation (if multiple versions exist)" }), - ...FlagsProvider.force, zip: Flags.boolean({ default: false, description: "Download the generated SDK as a .zip archive" }), - ...FlagsProvider.authKey + "track-changes": Flags.boolean({ + default: false, + description: "Generate SDK source tree in the src directory to enable tracking changes across generations" + }), + ...FlagsProvider.input, + ...FlagsProvider.force, + ...FlagsProvider.authKey, }; static examples = [ @@ -47,22 +58,44 @@ Supports multiple programming languages including Java, C#, Python, JavaScript, async run() { const { - flags: { language, input, destination, force, zip: zipSdk, "auth-key": authKey, "api-version": apiVersion } + flags: { language, input, destination, force, zip: zipSdk, "auth-key": authKey, "skip-changes": skipChanges, "track-changes": trackChanges,"api-version": apiVersion } } = await this.parse(SdkGenerate); const workingDirectory = DirectoryPath.createInput(input); const buildDirectory = input ? new DirectoryPath(input, "src") : workingDirectory.join("src"); - const sdkDirectory = destination ? new DirectoryPath(destination) : DirectoryPath.default.join("sdk"); + const sdkDirectory = destination ? new DirectoryPath(destination) : workingDirectory.join("sdk"); const commandMetadata: CommandMetadata = { commandName: SdkGenerate.id, shell: this.config.shell }; + const telemetryService = new TelemetryService(this.getConfigDir()); intro("Generate SDK"); const action = new GenerateAction(this.getConfigDir(), commandMetadata, authKey); - const result = await action.execute(buildDirectory, sdkDirectory, language as Language, force, zipSdk, apiVersion); + const result = await action.execute( + buildDirectory, + sdkDirectory, + language as Language, + force, + zipSdk, + skipChanges, + trackChanges, + apiVersion + ); outro(result); + await result.mapAll( + async (res) => { + if (res?.sourceTreeTrackingInitiated) { + await telemetryService.trackEvent(new SdkChangesTrackedEvent(language), commandMetadata.shell); + } + if (res?.conflictsResolved) { + await telemetryService.trackEvent(new SdkConflictsResolvedEvent(language), commandMetadata.shell); + } + }, + async () => {}, + async () => {} + ); } private readonly getConfigDir = () => { diff --git a/src/commands/sdk/save-changes.ts b/src/commands/sdk/save-changes.ts new file mode 100644 index 00000000..d9d92103 --- /dev/null +++ b/src/commands/sdk/save-changes.ts @@ -0,0 +1,76 @@ +import { Command, Flags } from "@oclif/core"; +import { format, intro, outro } from "../../prompts/format.js"; +import { DirectoryPath } from "../../types/file/directoryPath.js"; +import { SaveChangesAction } from "../../actions/sdk/save-changes.js"; +import { Language } from "../../types/sdk/generate.js"; +import { FlagsProvider } from "../../types/flags-provider.js"; +import { TelemetryService } from "../../infrastructure/services/telemetry-service.js"; +import { CommandMetadata } from "../../types/common/command-metadata.js"; +import { SdkChangesSavedEvent } from "../../types/events/sdk-changes-saved.js"; + +export default class SaveChanges extends Command { + static readonly summary = "Save customizations made to an auto-generated SDK"; + + static readonly description = + "Requires an input directory with API specifications, a path to the updated SDK directory, and the programming language."; + + static readonly cmdTxt = format.cmd("apimatic", "sdk", "save-changes"); + + static flags = { + language: Flags.string({ + char: "l", + required: true, + description: "Programming language of the SDK", + options: Object.values(Language).map((p) => p.valueOf()) + }), + "sdk": Flags.string({ + description: "[default: ./sdk/ | ./sdk//] path to the folder containing the updated SDK." + }), + "api-version": Flags.string({ + description: "Version of the API where changes should be saved (if multiple versions exist)." + }), + ...FlagsProvider.input, + }; + + static examples = [ + `${SaveChanges.cmdTxt} ${format.flag("language", "csharp")}`, + `${SaveChanges.cmdTxt} ${format.flag("language", "java")} ${format.flag("sdk", "./sdk")}` + ]; + + async run() { + const { + flags: { sdk, language, input, "api-version": apiVersion } + } = await this.parse(SaveChanges); + + const workingDirectory = DirectoryPath.createInput(input); + const buildDirectory = workingDirectory.join("src"); + const sdkDirectoryInput = sdk ? new DirectoryPath(sdk) : undefined; + + const commandMetadata: CommandMetadata = { + commandName: SaveChanges.id, + shell: this.config.shell + }; + const telemetryService = new TelemetryService(this.getConfigDir()); + + intro("Save Changes"); + const action = new SaveChangesAction(); + const result = await action.execute( + workingDirectory, + buildDirectory, + sdkDirectoryInput, + language as Language, + apiVersion + ); + outro(result); + + await result.mapAll( + async () => await telemetryService.trackEvent(new SdkChangesSavedEvent(language), commandMetadata.shell), + async () => {}, + async () => {} + ); + } + + private readonly getConfigDir = () => { + return new DirectoryPath(this.config.configDir); + }; +} diff --git a/src/hooks/not-found.ts b/src/hooks/not-found.ts index b2cd7a4c..7c23a2d2 100644 --- a/src/hooks/not-found.ts +++ b/src/hooks/not-found.ts @@ -46,7 +46,7 @@ const hook: Hook.CommandNotFound = async function (opts) { if (response) { const confirmedSuggestion = suggestion!; - let argv = opts.argv?.length ? opts.argv : opts.id.split(":").slice(confirmedSuggestion.split(":").length); + let argv = opts.argv ?? []; if (confirmedSuggestion.startsWith("help:")) { argv = confirmedSuggestion.split(":").slice(1); diff --git a/src/infrastructure/file-service.ts b/src/infrastructure/file-service.ts index 0df0fa14..2b8e2f60 100644 --- a/src/infrastructure/file-service.ts +++ b/src/infrastructure/file-service.ts @@ -9,6 +9,7 @@ import { Directory } from "../types/file/directory.js"; import { FileName } from "../types/file/fileName.js"; export class FileService { + public async fileExists(file: FilePath): Promise { try { const stat = await fsExtra.stat(file.toString()); @@ -41,6 +42,19 @@ export class FileService { await fsExtra.emptyDir(dir.toString()); // removes everything inside, keeps the dir } + public async cleanDirectoryExcluding(dir: DirectoryPath, excludeNames: FileName[]): Promise { + await fsExtra.ensureDir(dir.toString()); + const entries = await fsExtra.readdir(dir.toString()); + + await Promise.all( + entries + .filter((entry) => !excludeNames.find((exclude) => exclude.toString() === entry)) + .map(async (entry) => { + return fsExtra.remove(dir.join(entry).toString()); + }) + ); + } + public async createDirectoryIfNotExists(dir: DirectoryPath): Promise { await fsExtra.ensureDir(dir.toString()); } @@ -51,22 +65,30 @@ export class FileService { entries.map(async (entry) => { const fullPath = path.join(directoryPath.toString(), entry); const stat = await fsExtra.stat(fullPath); - return stat.isDirectory() ? await this.getDirectory(new DirectoryPath(fullPath)) : new FileName(entry); + return stat.isDirectory() ? await this.getDirectory(new DirectoryPath(fullPath)) : { fileName: new FileName(entry) }; }) ); return new Directory(directoryPath, results); } public async getSubDirectoriesPaths(dir: DirectoryPath): Promise { - try { - const entries = await fsExtra.readdir(dir.toString(), { withFileTypes: true }); - return entries - .filter((entry) => entry.isDirectory()) - .map((entry) => dir.join(entry.name)); - } catch { - return []; + try { + const entries = await fsExtra.readdir(dir.toString()); + const directories: DirectoryPath[] = []; + + for (const entry of entries) { + const fullPath = dir.join(entry).toString(); + const stat = await fsExtra.stat(fullPath); + if (stat.isDirectory()) { + directories.push(new DirectoryPath(fullPath)); + } + } + + return directories; + } catch { + return []; + } } -} public async copyDirectoryContents(source: DirectoryPath, destination: DirectoryPath) { const entries = await fsExtra.readdir(source.toString()); @@ -79,6 +101,32 @@ export class FileService { ); } + public async copyDirectoryExcluding( + source: DirectoryPath, + destination: DirectoryPath, + excludeNames: FileName[] + ): Promise { + await this.createDirectoryIfNotExists(destination); + + const entries = await fsExtra.readdir(source.toString()); + + await Promise.all( + entries + .filter((entry) => !excludeNames.find((exclude) => exclude.toString() === entry)) + .map(async (entry) => { + const sourcePath = path.join(source.toString(), entry); + const destPath = path.join(destination.toString(), entry); + const stat = await fsExtra.stat(sourcePath); + + if (stat.isDirectory()) { + return this.copyDirectoryExcluding(new DirectoryPath(sourcePath), new DirectoryPath(destPath), excludeNames); + } + + return fsExtra.copyFile(sourcePath, destPath); + }) + ); + } + public async deleteFile(filePath: FilePath): Promise { const exists = await this.fileExists(filePath); if (exists) { @@ -93,6 +141,20 @@ export class FileService { } } + public async pollDeleteDirectory(dirPath: DirectoryPath, onDeleteFailurePersists: () => void): Promise { + const timeoutMs = 5 * 60 * 1000; + const deadline = Date.now() + timeoutMs; + const deleteFailurePersistsMaxDelay = Date.now() + 5 * 1000; + let actionPerformed = false; + while (Date.now() < deadline && await this.deleteDirectory(dirPath).then(() => false).catch(() => true)) { + if (!actionPerformed && Date.now() > deleteFailurePersistsMaxDelay) { + onDeleteFailurePersists(); + actionPerformed = true; + } + await new Promise((resolve) => setTimeout(resolve, 500)); + } + } + public getRelativePath(filePath: FilePath, basePath: DirectoryPath): string { const filePathStr = filePath.toString(); const basePathStr = basePath.toString(); @@ -135,6 +197,10 @@ export class FileService { await fsExtra.copyFile(source.toString(), source.replaceDirectory(destination).toString()); } + public async readFile(filePath: FilePath): Promise { + return await fsExtra.readFile(filePath.toString(), "utf-8"); + } + public async isZipFile(filePath: FilePath): Promise { try { const buffer = await fsExtra.readFile(filePath.toString()); @@ -149,6 +215,10 @@ export class FileService { return false; } } + + public async hasContent(file: FilePath, content: string): Promise { + return await this.fileExists(file) && (await this.readFile(file)).includes(content); + } } const streamPipeline = promisify(pipeline); diff --git a/src/infrastructure/git-service.ts b/src/infrastructure/git-service.ts new file mode 100644 index 00000000..3148bef5 --- /dev/null +++ b/src/infrastructure/git-service.ts @@ -0,0 +1,128 @@ +import git from "isomorphic-git"; +import fs from "fs"; +import { DirectoryPath } from "../types/file/directoryPath.js"; +import { FileName } from "../types/file/fileName.js"; +import { FilePath } from "../types/file/filePath.js"; +import { Directory, FileItem } from "../types/file/directory.js"; + +const GIT_AUTHOR = { name: "APIMatic-bot", email: "developer@apimatic.io" } as const; +const CUSTOM_BRANCH = "custom-code"; +const MAIN_BRANCH = "main"; + +export class GitService { + public async checkoutCustomBranch(dirPath: DirectoryPath): Promise { + const dir = dirPath.toString(); + + if (!await this.hasCustomBranch(dirPath)) { + await git.branch({ fs, dir, ref: CUSTOM_BRANCH }); + } + + await git.checkout({ fs, dir, ref: CUSTOM_BRANCH}); + } + + public async hasCustomBranch(dirPath: DirectoryPath): Promise { + const dir = dirPath.toString(); + const branches = await git.listBranches({ fs, dir }); + return branches.includes(CUSTOM_BRANCH); + } + + public getMergeFiles(dirPath: DirectoryPath): FilePath[] { + const gitDir = dirPath.join(".git"); + const mergeFiles = ["MERGE_HEAD", "MERGE_MODE", "MERGE_MSG"]; + return mergeFiles.map((filename) => new FilePath(gitDir, new FileName(filename))); + } + + public async forceCheckoutMainBranch(dirPath: DirectoryPath): Promise { + await git.checkout({ fs, dir: dirPath.toString(), ref: MAIN_BRANCH, force: true }); + } + + public async hardReset(dir: DirectoryPath): Promise { + await git.checkout({ fs, dir: dir.toString(), force: true }); + } + + public async getDirectoryWithUpdatedFiles(dirPath: DirectoryPath): Promise { + const statusMatrix = await git.statusMatrix({ fs, dir: dirPath.toString() }); + + const relativeUncommitedFiles = statusMatrix + // include updated and resolved conflict files + .filter(([, headStatus, workdirStatus, stageStatus]) => headStatus !== workdirStatus || stageStatus === 3) + .map(([relativePath, workdirStatus, headStatus]) => { + if (headStatus === 0) { + return { relativePath, description: "# Deleted" }; + } + if (workdirStatus === 0) { + return { relativePath, description: "# Added" }; + } + return { relativePath, description: "# Modified" }; + }); + + type PendingDirectoryItem = FileItem | PendingDir; + type PendingDir = { pendingDirPath: DirectoryPath; items: PendingDirectoryItem[] }; + + const buildDirectory = (pending: PendingDir): Directory => new Directory( + pending.pendingDirPath, + pending.items.map((item) => ("pendingDirPath" in item ? buildDirectory(item) : item)) + ); + + const root: PendingDir = { pendingDirPath: dirPath, items: [] }; + for (const file of relativeUncommitedFiles) { + const parts = file.relativePath.split(/[\\/]/); + let currentDir = root; + + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + const isLastPart = i === parts.length - 1; + + if (isLastPart) { + currentDir.items.push({ fileName: new FileName(part), description: file.description }); + } else { + let existingDir = currentDir.items.find( + (item) => "pendingDirPath" in item && item.pendingDirPath.leafName() === part + ) as PendingDir | undefined; + + if (!existingDir) { + existingDir = { pendingDirPath: currentDir.pendingDirPath.join(part), items: [] }; + currentDir.items.push(existingDir); + } + + currentDir = existingDir; + } + } + } + + return buildDirectory(root); + } + + private async stageAll(dir: DirectoryPath): Promise { + const statusMatrix = await git.statusMatrix({ fs, dir: dir.toString() }); + await Promise.all( + statusMatrix + // include updated and resolved conflict files + .filter(([, headStatus, workdirStatus, stageStatus]) => headStatus !== workdirStatus || stageStatus === 3) + .map(([relativePath, , workdirStatus]) => { + if (workdirStatus === 0) { + return git.remove({ fs, dir: dir.toString(), filepath: relativePath }); + } + return git.add({ fs, dir: dir.toString(), filepath: relativePath }); + }) + ); + } + + public getConflictMarker(): string { + return "<<<<<<< "; + } + + public async commitResolvedConflicts(dir: DirectoryPath): Promise { + await this.stageAll(dir); + await git.commit({ fs, dir: dir.toString(), message: "feat: resolve merge conflicts", author: GIT_AUTHOR }); + + const headOid = await git.resolveRef({ fs, dir: dir.toString(), ref: "HEAD" }); + await git.writeRef({ fs, dir: dir.toString(), ref: `refs/heads/${MAIN_BRANCH}`, value: headOid, force: true }); + await git.writeRef({ fs, dir: dir.toString(), ref: `refs/heads/${CUSTOM_BRANCH}`, value: headOid, force: true }); + } + + public async commitReviewedChanges(dir: DirectoryPath): Promise { + await this.stageAll(dir); + await git.commit({ fs, dir: dir.toString(), message: "feat: add customizations to generated SDK", author: GIT_AUTHOR }); + } +} diff --git a/src/infrastructure/launcher-service.ts b/src/infrastructure/launcher-service.ts index 43082d61..dabf19f4 100644 --- a/src/infrastructure/launcher-service.ts +++ b/src/infrastructure/launcher-service.ts @@ -11,7 +11,21 @@ export class LauncherService { public async openFolderInIde(directoryPath: DirectoryPath, fileToOpen: FilePath): Promise { if (isInCi) return false; try { - await execa("code", [directoryPath.toString(), fileToOpen.toString()]); + const args = [directoryPath.toString(), fileToOpen.toString()]; + await execa("code", args); + return true; + } catch { + return false; + } + } + + public async openFolderInIdeWithWait( + directoryPath: DirectoryPath, + filesToOpen: FilePath[] + ): Promise { + try { + const args = [directoryPath.toString(), ...filesToOpen.map(f => f.toString())]; + await execa("code", ["--new-window", "--wait", ...args]); return true; } catch { return false; diff --git a/src/infrastructure/service-error.ts b/src/infrastructure/service-error.ts index 96f0aab7..fdb1f01b 100644 --- a/src/infrastructure/service-error.ts +++ b/src/infrastructure/service-error.ts @@ -1,21 +1,31 @@ import axios from "axios"; import { format as f } from "../prompts/format.js"; +export enum ServiceErrorCode { + NotFound = "NOT_FOUND", + ServerError = "SERVER_ERROR", + NetworkError = "NETWORK_ERROR", + InvalidResponse = "INVALID_RESPONSE", + UnAuthorized = "UNAUTHORIZED", + BadRequest = "BAD_REQUEST", + Forbidden = "FORBIDDEN" +} + export class ServiceError { private static defaultErrorMessage = `An unexpected error occurred, please try again later. If the problem persists, please reach out to our team at ${f.var( "support@apimatic.io" )}`; - static readonly NotFound = new ServiceError("NOT_FOUND", "Resource not found."); - static readonly ServerError = new ServiceError("SERVER_ERROR", this.defaultErrorMessage); - static readonly NetworkError = new ServiceError("NETWORK_ERROR", "Unable to connect to the server."); - static readonly InvalidResponse = new ServiceError("INVALID_RESPONSE", this.defaultErrorMessage); - static readonly UnAuthorized = new ServiceError("UNAUTHORIZED", "Unauthorized access."); - static badRequest(customMessage: string): ServiceError { - return new ServiceError("BAD_REQUEST", customMessage); + static readonly NotFound = new ServiceError(ServiceErrorCode.NotFound, "Resource not found.", {}); + static readonly ServerError = new ServiceError(ServiceErrorCode.ServerError, this.defaultErrorMessage, {}); + static readonly NetworkError = new ServiceError(ServiceErrorCode.NetworkError, "Unable to connect to the server.", {}); + static readonly InvalidResponse = new ServiceError(ServiceErrorCode.InvalidResponse, this.defaultErrorMessage, {}); + static readonly UnAuthorized = new ServiceError(ServiceErrorCode.UnAuthorized, "Unauthorized access.", {}); + static badRequest(customMessage: string, errors: Record): ServiceError { + return new ServiceError(ServiceErrorCode.BadRequest, customMessage, errors); } static forbidden(customMessage: string): ServiceError { - return new ServiceError("FORBIDDEN", customMessage); + return new ServiceError(ServiceErrorCode.Forbidden, customMessage, {}); } static readonly values: ServiceError[] = [ @@ -26,11 +36,19 @@ export class ServiceError { ServiceError.UnAuthorized ]; - private constructor(public readonly code: string, private readonly defaultMessage: string) {} + private constructor( + public readonly code: ServiceErrorCode, + private readonly defaultMessage: string, + private readonly errors: Record + ) {} public get errorMessage(): string { return this.defaultMessage; } + + public getError(key: string): string[] | undefined { + return this.errors[key]; + } } export function handleServiceError(error: unknown): ServiceError { diff --git a/src/infrastructure/services/portal-service.ts b/src/infrastructure/services/portal-service.ts index 92abab06..5dc9bc2c 100644 --- a/src/infrastructure/services/portal-service.ts +++ b/src/infrastructure/services/portal-service.ts @@ -5,6 +5,7 @@ import { SdkGenerationAsyncController, ContentType, DocsPortalGenerationAsyncController, + SdkSourceTreeGenerationAsyncController, ProblemDetailsError, FileWrapper, TransformationController, @@ -27,6 +28,11 @@ import { Language } from "../../types/sdk/generate.js"; import { handleServiceError, ServiceError } from "../service-error.js"; import { ApiService } from "./api-service.js"; +export interface GeneratedSdkResult { + sdk: NodeJS.ReadableStream; + sdkSourceTree: NodeJS.ReadableStream; +} + export class PortalService { private readonly CONTENT_TYPE = ContentType.EnumMultipartformdata; private readonly fileService = new FileService(); @@ -56,11 +62,12 @@ export class PortalService { generationId = portalInstance.result.id; } catch (error) { if (error instanceof ProblemDetailsError) { + const errors = error.result!.errors as Record; // TODO: This only picks the first error message, improve it to show all errors. - const message = Object.values(error.result!.errors as Record)[0]?.[0] ?? null; + const message = Object.values(errors)[0]?.[0] ?? null; const errorMessage = error.result!.title + "\n- " + message; if (error.statusCode === 400) { - return err(ServiceError.badRequest(errorMessage)); + return err(ServiceError.badRequest(errorMessage, errors)); } if (error.statusCode === 403) { return err(ServiceError.forbidden(errorMessage)); @@ -88,14 +95,17 @@ export class PortalService { return err(ServiceError.ServerError); } if (statusResult.value.errors && statusResult.value.status === Status.ValidationError) { - // TODO: This only picks the first error message, improve it to show all errors. - const message = Object.values(statusResult.value.errors as Record)[0]?.[0] ?? null; - const errorMessage = "One or more validation errors occurred." + "\n- " + message; - return err(ServiceError.badRequest(errorMessage)); + const errors = statusResult.value.errors as Record; + const message = Object.values(errors)[0]?.[0] ?? null; + const errorMessage = + "One or more validation errors occurred." + + "\n- " + message; + return err(ServiceError.badRequest(errorMessage, errors)); } if (statusResult.value.errors && statusResult.value.status === Status.SubscriptionError) { + const errors = statusResult.value.errors as Record; // TODO: This only picks the first error message, improve it to show all errors. - const message = Object.values(statusResult.value.errors as Record)[0]?.[0] ?? null; + const message = Object.values(errors)[0]?.[0] ?? null; const errorMessage = "Access denied to resource." + "\n- " + message; return err(ServiceError.forbidden(errorMessage)); } @@ -119,7 +129,7 @@ export class PortalService { configDir: DirectoryPath, commandMetadata: CommandMetadata, authKey: string | null - ): Promise> { + ): Promise> { const buildFileStream = await this.fileService.getStream(buildPath); const file = new FileWrapper(buildFileStream); @@ -139,10 +149,11 @@ export class PortalService { } catch (error) { if (error instanceof ProblemDetailsError) { // TODO: This only picks the first error message, improve it to show all errors. - const message = Object.values(error.result!.errors as Record)[0]?.[0] ?? null; + const errors = error.result!.errors as Record; + const message = Object.values(errors)[0]?.[0] ?? null; const errorMessage = error.result!.title + "\n- " + message; if (error.statusCode === 400) { - return err(ServiceError.badRequest(errorMessage)); + return err(ServiceError.badRequest(errorMessage, errors)); } if (error.statusCode === 403) { return err(ServiceError.forbidden(errorMessage)); @@ -171,12 +182,25 @@ export class PortalService { return err(ServiceError.ServerError); } if (statusResult.value.errors && statusResult.value.status === Status.ValidationError) { - const message = Object.values(statusResult.value.errors as Record).flat()[0] ?? null; - const errorMessage = "One or more validation errors occurred." + "\n- " + message; - return err(ServiceError.badRequest(errorMessage)); + const errors = statusResult.value.errors as Record; + const sdkMergeFailedLanguages = errors.sdkMergeFailed; + if (sdkMergeFailedLanguages?.length) { + const errorMessage = + "SDK generation failed for these languages due to merge conflict." + + "\n- " + + sdkMergeFailedLanguages.join("\n- "); + return err(ServiceError.badRequest(errorMessage, errors)); + } + + const messages = Object.values(errors).flat(); + const errorMessage = + "One or more validation errors occurred." + + (messages.length ? "\n- " + messages.join("\n- ") : ""); + return err(ServiceError.badRequest(errorMessage, errors)); } if (statusResult.value.errors && statusResult.value.status === Status.SubscriptionError) { - const message = Object.values(statusResult.value.errors as Record).flat()[0] ?? null; + const errors = statusResult.value.errors as Record; + const message = Object.values(errors).flat()[0] ?? null; const errorMessage = "Access denied to resource." + "\n- " + message; return err(ServiceError.forbidden(errorMessage)); } @@ -184,7 +208,12 @@ export class PortalService { try { const sdkResponse = await sdkGenerationController.downloadGeneratedSdk(generationId); - return ok(sdkResponse.result as NodeJS.ReadableStream); + const sdkSourceTreeController = new SdkSourceTreeGenerationAsyncController(client); + const sdkSourceTreeResponse = await sdkSourceTreeController.downloadGeneratedSdkSourceTree(generationId); + return ok({ + sdk: sdkResponse.result as NodeJS.ReadableStream, + sdkSourceTree: sdkSourceTreeResponse.result as NodeJS.ReadableStream + }); } catch (error) { return err(handleServiceError(error)); } diff --git a/src/infrastructure/zip-service.ts b/src/infrastructure/zip-service.ts index 6a00d639..c65a928f 100644 --- a/src/infrastructure/zip-service.ts +++ b/src/infrastructure/zip-service.ts @@ -14,7 +14,7 @@ export class ZipService { archive.on("error", (err) => reject(err)); archive.pipe(output); - archive.directory(sourceDir.toString(), false); // false: don't nest under folder + archive.directory(sourceDir.toString(), false); archive.finalize(); }); } diff --git a/src/prompts/format.ts b/src/prompts/format.ts index 9769203c..3c5768c6 100644 --- a/src/prompts/format.ts +++ b/src/prompts/format.ts @@ -95,7 +95,6 @@ export interface TreeNode extends LeafNode { items: Array; } - export function getTree( dir: TreeNode, prefix: string = "", @@ -125,5 +124,3 @@ export function getTree( return output; } - - diff --git a/src/prompts/portal/generate.ts b/src/prompts/portal/generate.ts index 1fd3d82f..724f5370 100644 --- a/src/prompts/portal/generate.ts +++ b/src/prompts/portal/generate.ts @@ -4,7 +4,7 @@ import { format as f } from "../format.js"; import { Result } from "neverthrow"; import { FilePath } from "../../types/file/filePath.js"; import {ServiceError } from "../../infrastructure/service-error.js"; -import { withSpinner } from "../prompt.js"; +import { noteWrapped, withSpinner } from "../prompt.js"; export class PortalGeneratePrompts { public async overwritePortal(directory: DirectoryPath): Promise { @@ -45,8 +45,12 @@ export class PortalGeneratePrompts { log.error(error); } - public portalGenerationServiceError(serviceError: ServiceError) { - log.error(serviceError.errorMessage); + public portalGenerationSdkMergeFailed(sdkMergeFailedErrors: string[]) { + log.error(`While generating portal, there were merge conflicts in the following SDKs:\n- ${sdkMergeFailedErrors.join("\n- ")}`); + const message = `Run the command +'${f.cmdAlt("apimatic", "sdk", "generate", f.flag("language", ""))}' +interactively to review and resolve the conflicts with SDK generation.`; + noteWrapped(message, "Next Steps"); } public portalGenerationErrorWithReport(reportPath: FilePath) { @@ -58,4 +62,4 @@ A report has been written at the destination path ${f.path(reportPath)}`; public portalGenerated(portal: DirectoryPath) { log.info(`Portal artifacts can be found at ${f.path(portal)}.`); } -} +} \ No newline at end of file diff --git a/src/prompts/sdk/generate.ts b/src/prompts/sdk/generate.ts index ba8cec03..02a8f07b 100644 --- a/src/prompts/sdk/generate.ts +++ b/src/prompts/sdk/generate.ts @@ -1,9 +1,11 @@ import { isCancel, confirm, log, select } from "@clack/prompts"; import { DirectoryPath } from "../../types/file/directoryPath.js"; -import { format as f, } from "../format.js"; +import { format as f } from "../format.js"; import { Result } from "neverthrow"; import { withSpinner } from "../prompt.js"; import { ServiceError } from "../../infrastructure/service-error.js"; +import { GeneratedSdkResult } from "../../infrastructure/services/portal-service.js"; +import { Language } from "../../types/sdk/generate.js"; export class SdkGeneratePrompts { public async overwriteSdk(directory: DirectoryPath): Promise { @@ -20,40 +22,47 @@ export class SdkGeneratePrompts { } public sameBuildAndSdkDir(directory: DirectoryPath) { - const message = `The ${f.var("src")} and ${f.var("sdk")} directories must be different. Current value: ${f.path( - directory - )}`; this.logGenerationError(message); + const message = + `The ${f.var("src")} and ${f.var("sdk")} directories must be different. ` + + `Current value: ${f.path(directory)}`; + log.error(message); + } + + public srcDirectoryEmpty(directory: DirectoryPath) { + const message = `The ${f.var("src")} directory is either empty or invalid: ${f.path(directory)}`; + log.error(message); } public specDirectoryEmpty(directory: DirectoryPath) { - const message = `The ${f.var("spec")} directory is either empty or invalid: ${f.path(directory)}`; - this.logGenerationError(message); + const message = `The ${f.var('spec')} directory is either empty or invalid: ${f.path(directory)}`; + log.error(message); } public destinationDirNotEmpty() { const message = `Please enter a different destination folder or remove the existing files and try again.`; - this.logGenerationError(message); + log.error(message); } - public generateSDK(fn: Promise>) { + public generateSDK(fn: Promise>) { return withSpinner("Generating SDK", "SDK generated successfully.", "SDK Generation failed.", fn); } - public logGenerationError(error: string): void { - log.error(error); - } - public sdkGenerationServiceError(serviceError: ServiceError) { log.error(serviceError.errorMessage); } - public versionedBuildEmpty(directory: DirectoryPath) { - const message = `The ${f.var(directory.leafName())} directory is either empty or invalid: ${f.path(directory)}`; - this.logGenerationError(message); + public invalidVersionedDocsDirectory(directory: DirectoryPath) { + const message = `The ${f.var("versioned_docs")} directory is either empty or invalid: ${f.path(directory)}`; + log.error(message); + } + + public apiVersionOnlyApplicableWithVersionedBuild() { + log.warn(`The ${f.flag("api-version")} is only applicable with a versioned build.`); } public versionNotFound() { - this.logGenerationError(`The API version is invalid.`); + const message = `The selected API version is invalid.`; + log.error(message); } public async selectVersion(versions: string[]): Promise { diff --git a/src/prompts/sdk/merge-source-tree.ts b/src/prompts/sdk/merge-source-tree.ts new file mode 100644 index 00000000..50c63061 --- /dev/null +++ b/src/prompts/sdk/merge-source-tree.ts @@ -0,0 +1,91 @@ +import { isCancel, log, confirm } from "@clack/prompts"; +import { format as f, getTree } from "../format.js"; +import { DirectoryPath } from "../../types/file/directoryPath.js"; +import { Language } from "../../types/sdk/generate.js"; +import { Directory } from "../../types/file/directory.js"; +import { noteWrapped } from "../prompt.js"; +import { FilePath } from "../../types/file/filePath.js"; + +export class MergeSourceTreePrompts { + public successfullySkippedChanges(language: Language) { + log.info(`Skipped customizations for ${f.var(language)} SDK.`); + } + + public successfullyAppliedChanges(language: Language) { + log.info(`Successfully applied customizations for ${f.var(language)} SDK.`); + } + + public changeTrackingEnabled(language: Language, destinationSourceTreePath: FilePath) { + log.info(`Change tracking is enabled for ${f.var(language)}. The 'sdk-source-tree' has been saved to ${f.path(destinationSourceTreePath)}.`); + + const message = `Customize your SDK and run the command +'${f.cmdAlt("apimatic", "sdk", "save-changes", f.flag("language", language))}' +to save and persist your changes for the future SDK generations.`; + noteWrapped(message, "Next Steps"); + } + + public changeTrackingAlreadyEnabled(language: Language) { + const message = + `Change tracking is already enabled for ${f.var(language)}. ` + + `No need to use the ${f.flag("track-changes")} flag again for ${f.var(language)} SDK.`; + log.warn(message); + } + + public sdkGenerated(sdk: DirectoryPath) { + log.info(`Generated SDK can be found at ${f.path(sdk)}.`); + } + + public sdkGeneratedWithSourceTree(sdk: DirectoryPath, sourceTree: FilePath) { + log.info(`Generated SDK can be found at ${f.path(sdk)},\n and the updated 'sdk-source-tree' can be found at ${f.path(sourceTree)}.`); + } + + public errorMergeConflicts(language: Language) { + log.error(`Merge conflicts detected in the generated ${f.var(language)} SDK.`); + + const message = `Run the command +'${f.cmdAlt("apimatic", "sdk", "generate", f.flag("language", language))}' +interactively to review and resolve the conflicts with SDK generation.`; + noteWrapped(message, "Next Steps"); + } + + public conflictsDetected(language: Language, directory: Directory) { + log.message(`Conflicts found in ${f.var(language)} SDK:`); + log.message(getTree(directory.toTreeNode())); + } + + public conflictsStillPresent(directory: Directory) { + log.warn("Conflicts are still present. Please resolve all conflicts and try again."); + log.message(getTree(directory.toTreeNode())); + } + + public openingDirectoryForConflictResolution(language: Language) { + log.info(`Opening ${f.var(language)} SDK in VS Code for conflicts resolution.`); + } + + public async waitForConflictsResolved(language: Language, sdkDir: DirectoryPath): Promise { + log.info(`Unable to open VS Code. Please resolve all conflicts in the ${f.var(language)} SDK at ${f.path(sdkDir)} to proceed.`); + const confirmed = await confirm({ + message: `Have you resolved all conflicts?`, + initialValue: false + }); + + if (isCancel(confirmed)) { + return false; + } + + return confirmed; + } + + public conflictsResolved(language: Language) { + log.info(`All conflicts resolved for ${f.var(language)} SDK.`); + } + + public operationCancelled() { + log.info("Exiting without resolving conflicts."); + } + + public async directoryStillOpen(directory: DirectoryPath) { + log.info(`Please close all applications using the directory ${f.path(directory)} to allow cleanup of temporary files.`); + } + +} diff --git a/src/prompts/sdk/save-changes.ts b/src/prompts/sdk/save-changes.ts new file mode 100644 index 00000000..49384ad0 --- /dev/null +++ b/src/prompts/sdk/save-changes.ts @@ -0,0 +1,106 @@ +import { log, isCancel, select, confirm } from "@clack/prompts"; +import { DirectoryPath } from "../../types/file/directoryPath.js"; +import { format as f, getTree } from "../format.js"; +import { noteWrapped } from "../prompt.js"; +import { FilePath } from "../../types/file/filePath.js"; +import { Directory } from "../../types/file/directory.js"; + +export class SaveChangesPrompts { + + public srcDirectoryEmpty(directory: DirectoryPath) { + const message = `The ${f.var("src")} directory is either empty or invalid: ${f.path(directory)}`; + log.error(message); + } + + public invalidSdkDirectory(directory: DirectoryPath) { + const message = `SDK directory does not exist: ${f.path(directory)}`; + log.error(message); + } + + public invalidVersionedDocsDirectory(directory: DirectoryPath) { + const message = `The ${f.var("versioned_docs")} directory is either empty or invalid: ${f.path(directory)}`; + log.error(message); + } + + public apiVersionOnlyApplicableWithVersionedBuild() { + log.warn(`The ${f.flag("api-version")} is only applicable with a versioned build.`); + } + + public versionNotFound() { + log.error(`The selected API version is invalid.`); + } + + public async selectVersion(versions: string[]): Promise { + const version = await select({ + message: "Select an API version to save SDK changes into:", + options: versions.map((v) => ({ label: v, value: v })) + }); + + if (isCancel(version)) { + return undefined; + } + + return version; + } + + public sdkSourceTreeNotFound(language: string) { + log.error(`No 'sdk-source-tree' found for ${f.var(language)}.`); + const message = `Run the command +'${f.cmdAlt("apimatic", "sdk", "generate", f.flag("language", language), f.flag("track-changes"))}' +to generate the SDK with 'sdk-source-tree'.`; + noteWrapped(message, "Next Steps"); + } + + public operationCancelled() { + log.info("Exiting without saving any changes."); + } + + public async directoryStillOpen(directory: DirectoryPath) { + log.info(`Please close all applications using the directory ${f.path(directory)} to allow cleanup of temporary files.`); + } + + public modifiedFilesDetected(directory: Directory) { + log.message(`Detected changes in the following file(s):`); + log.message(getTree(directory.toTreeNode())); + } + + public noChangesDetected() { + log.info("No changes detected in the SDK."); + } + + public openingDirectoryToReviewChanges() { + log.info(`Opening the changed files in VS Code for review.`); + } + + public async reviewChangesManually(tempDirectory: DirectoryPath): Promise { + + log.info(`Unable to open VS Code. Review the changes at ${f.path(tempDirectory)} to proceed.`); + const confirmed = await confirm({ + message: `Do you want to save these changes?`, + initialValue: false + }); + + if (isCancel(confirmed)) { + return false; + } + + return confirmed; + } + + public async confirmChanges(): Promise { + const confirmed = await confirm({ + message: `Do you want to review these changes?`, + initialValue: false + }); + + if (isCancel(confirmed)) { + return false; + } + + return confirmed; + } + + public changesSaved(sourceTreePath: FilePath) { + log.success(`Changes saved successfully at ${f.path(sourceTreePath)}.`); + } +} diff --git a/src/types/build-context.ts b/src/types/build-context.ts index f5851b18..e2079bc1 100644 --- a/src/types/build-context.ts +++ b/src/types/build-context.ts @@ -3,6 +3,7 @@ import { DirectoryPath } from "./file/directoryPath.js"; import { FilePath } from "./file/filePath.js"; import { FileName } from "./file/fileName.js"; import { BuildConfig } from "./build/build.js"; +import { SpecContext } from "./spec-context.js"; export class BuildContext { private readonly fileService = new FileService(); @@ -24,10 +25,6 @@ export class BuildContext { return await this.fileService.fileExists(this.buildFile); } - public async exists(): Promise { - return !(await this.fileService.directoryEmpty(this.buildDirectory)); - } - public async getBuildFileContents(): Promise { const buildFileContent = await this.fileService.getContents(this.buildFile); return JSON.parse(buildFileContent) as BuildConfig; @@ -40,5 +37,77 @@ export class BuildContext { public async deleteWorkflowDir() { await this.fileService.deleteDirectory(this.buildDirectory.join(".github")); } + + public getBuildDirectory(): DirectoryPath { + return this.buildDirectory; + } + + public getSpecContext(): SpecContext { + return new SpecContext(this.buildDirectory.join("spec")); + } + + public async hasSdkSourceTree(language: string): Promise { + const sourceTreePath = FilePath.create(this.buildDirectory.join("sdk-source-tree").join(`.${language}`).toString()); + if (!sourceTreePath) { + return false; + } + return await this.fileService.fileExists(sourceTreePath); + } + + public getSdkSourceTree(language: string): FilePath { + return FilePath.create(this.buildDirectory.join("sdk-source-tree").join(`.${language}`).toString())!; + } + + public async isVersionedBuild(): Promise { + const buildConfig = await this.getBuildFileContents(); + if (buildConfig.generateVersionedPortal) { + return true; + } + return false; + } + + public async getVersionedBuildDirectory(): Promise { + const buildConfig = await this.getBuildFileContents(); + if (!buildConfig.generateVersionedPortal) { + return undefined; + } + const versionsDirectory = this.buildDirectory.join(buildConfig.versionsPath ?? 'versioned_docs'); + if (!await this.fileService.directoryExists(versionsDirectory)) { + return undefined; + } + const versionsDirs = await this.fileService.getSubDirectoriesPaths(versionsDirectory); + return versionsDirs.length > 0 ? versionsDirectory : undefined; + } + + public async getSingleVersionedBuildDirectory(): Promise { + const buildConfig = await this.getBuildFileContents(); + if (!buildConfig.generateVersionedPortal) { + return undefined; + } + const versionsDirectory = this.buildDirectory.join(buildConfig.versionsPath ?? 'versioned_docs'); + if (!await this.fileService.directoryExists(versionsDirectory)) { + return undefined; + } + const versionsDirs = await this.fileService.getSubDirectoriesPaths(versionsDirectory); + return versionsDirs.length === 1 ? versionsDirs[0] : undefined; + } + + public async getSelectedVersionedBuildDirectory(versionSelector: (versions: string[]) => Promise): Promise { + const buildConfig = await this.getBuildFileContents(); + if (!buildConfig.generateVersionedPortal) { + return undefined; + } + const versionsDirectory = this.buildDirectory.join(buildConfig.versionsPath ?? 'versioned_docs'); + if (!await this.fileService.directoryExists(versionsDirectory)) { + return undefined; + } + const versionsDirs = await this.fileService.getSubDirectoriesPaths(versionsDirectory); + const selectedVersion = await versionSelector(versionsDirs.map(dir => dir.leafName())); + if (!selectedVersion) { + return undefined; + } + return versionsDirs.find(dir => dir.leafName() === selectedVersion); + + } } diff --git a/src/types/build/build.ts b/src/types/build/build.ts index 693eec04..1424b757 100644 --- a/src/types/build/build.ts +++ b/src/types/build/build.ts @@ -2,6 +2,8 @@ import { DirectoryPath } from "../file/directoryPath.js"; export interface BuildConfig { generatePortal?: PortalConfig; + generateVersionedPortal?: object; + versionsPath?: string; apiCopilotConfig?: CopilotConfig; [key: string]: unknown; } diff --git a/src/types/events/domain-event.ts b/src/types/events/domain-event.ts index e2ab6037..4766647c 100644 --- a/src/types/events/domain-event.ts +++ b/src/types/events/domain-event.ts @@ -14,4 +14,3 @@ export abstract class DomainEvent { return Object.keys(flags); } } - diff --git a/src/types/events/sdk-changes-saved.ts b/src/types/events/sdk-changes-saved.ts new file mode 100644 index 00000000..3b19cd33 --- /dev/null +++ b/src/types/events/sdk-changes-saved.ts @@ -0,0 +1,13 @@ +import { DomainEvent } from "./domain-event.js"; + +export class SdkChangesSavedEvent extends DomainEvent { + protected readonly eventName = SdkChangesSavedEvent.name; + private static readonly message = "Save changes completed." as const; + private static readonly commandName = "sdk:save-changes" as const; + private readonly language: string; + + constructor(language: string) { + super(SdkChangesSavedEvent.message, SdkChangesSavedEvent.commandName, {}); + this.language = language; + } +} diff --git a/src/types/events/sdk-changes-tracked.ts b/src/types/events/sdk-changes-tracked.ts new file mode 100644 index 00000000..c9c47e61 --- /dev/null +++ b/src/types/events/sdk-changes-tracked.ts @@ -0,0 +1,13 @@ +import { DomainEvent } from "./domain-event.js"; + +export class SdkChangesTrackedEvent extends DomainEvent { + protected readonly eventName = SdkChangesTrackedEvent.name; + private static readonly message = "SDK generated with track changes enabled." as const; + private static readonly commandName = "sdk:generate" as const; + private readonly language: string; + + constructor(language: string) { + super(SdkChangesTrackedEvent.message, SdkChangesTrackedEvent.commandName, {}); + this.language = language; + } +} diff --git a/src/types/events/sdk-conflicts-resolved.ts b/src/types/events/sdk-conflicts-resolved.ts new file mode 100644 index 00000000..69274067 --- /dev/null +++ b/src/types/events/sdk-conflicts-resolved.ts @@ -0,0 +1,13 @@ +import { DomainEvent } from "./domain-event.js"; + +export class SdkConflictsResolvedEvent extends DomainEvent { + protected readonly eventName = SdkConflictsResolvedEvent.name; + private static readonly message = "SDK merge conflicts resolved." as const; + private static readonly commandName = "sdk:generate" as const; + private readonly language: string; + + constructor(language: string) { + super(SdkConflictsResolvedEvent.message, SdkConflictsResolvedEvent.commandName, {}); + this.language = language; + } +} diff --git a/src/types/file/directory.ts b/src/types/file/directory.ts index 45fe3987..7b8ef2ab 100644 --- a/src/types/file/directory.ts +++ b/src/types/file/directory.ts @@ -5,7 +5,8 @@ import { FilePath } from "./filePath.js"; import { TreeNode } from "../../prompts/format.js"; import { FileService } from "../../infrastructure/file-service.js"; -export type DirectoryItem = FileName | Directory; +export type FileItem = { fileName: FileName, description?: string }; +export type DirectoryItem = FileItem | Directory; export class Directory { public readonly directoryPath: DirectoryPath; @@ -43,8 +44,8 @@ export class Directory { } // file case - const fileName = item.toString(); - const fileDescription = Directory.fileDescriptions[fileName]; + const fileName = item.fileName.toString(); + const fileDescription = item.description ?? Directory.fileDescriptions[fileName]; return { name: fileName, description: fileDescription @@ -53,6 +54,43 @@ export class Directory { }; } + public async mapFilesInDirectory(map: (rootDir: DirectoryPath, fileItem: FileItem) => Promise): Promise { + const mappedItems: DirectoryItem[] = []; + + for (const item of this.items) { + if (item instanceof Directory) { + const mappedSubDir = await item.mapFilesInDirectory(map); + if (!mappedSubDir.isEmpty()) { + mappedItems.push(mappedSubDir); + } + continue; + } + + const mappedItem = await map(this.directoryPath, item); + if (mappedItem) { + mappedItems.push(mappedItem); + } + } + + return new Directory(this.directoryPath, mappedItems); + } + + public isEmpty(): boolean { + return this.items.length === 0; + } + + public getAllFiles(): FilePath[] { + const files: FilePath[] = []; + for (const item of this.items) { + if (item instanceof Directory) { + files.push(...item.getAllFiles()); + } else { + files.push(new FilePath(this.directoryPath, item.fileName)); + } + } + return files; + } + public async parseContentFolder(baseContentPath: DirectoryPath): Promise { const groups: TocGroup[] = []; const pages: TocCustomPage[] = []; @@ -69,12 +107,12 @@ export class Directory { }); } } else { - if (item.isMarkDown()) { - const currentFilePath = new FilePath(this.directoryPath, item); + if (item.fileName.toString().endsWith(".md")) { + const currentFilePath = new FilePath(this.directoryPath, item.fileName); const relativeFilePath = this.fileService.getRelativePath(currentFilePath, baseContentPath); pages.push({ - page: this.getPageName(item), + page: this.getPageName(item.fileName), file: relativeFilePath }); } diff --git a/src/types/file/directoryPath.ts b/src/types/file/directoryPath.ts index fa3b8302..a3b87dc7 100644 --- a/src/types/file/directoryPath.ts +++ b/src/types/file/directoryPath.ts @@ -1,6 +1,5 @@ import * as path from "path"; - export class DirectoryPath { private readonly directoryPath: string; @@ -28,6 +27,7 @@ export class DirectoryPath { public isEqual(other: DirectoryPath) { return this.directoryPath === other.directoryPath; } + public leafName() { return path.basename(this.directoryPath); } diff --git a/src/types/merge-source-tree-context.ts b/src/types/merge-source-tree-context.ts new file mode 100644 index 00000000..aa023eec --- /dev/null +++ b/src/types/merge-source-tree-context.ts @@ -0,0 +1,81 @@ +import { FileService } from "../infrastructure/file-service.js"; +import { GitService } from "../infrastructure/git-service.js"; +import { ZipService } from "../infrastructure/zip-service.js"; +import { Directory } from "./file/directory.js"; +import { DirectoryPath } from "./file/directoryPath.js"; +import { FileName } from "./file/fileName.js"; +import { FilePath } from "./file/filePath.js"; + +export class MergeSourceTreeContext { + private readonly fileService = new FileService(); + private readonly zipService = new ZipService(); + private readonly gitService = new GitService(); + + constructor( + private readonly sdkWithSourceTree: DirectoryPath, + private readonly sdkWithoutSourceTree: DirectoryPath, + private readonly sourceTreePath: FilePath, + private readonly trackChanges: boolean, + private readonly skipChanges: boolean, + private readonly hasSdkSourceTree: boolean + ) { } + + public async saveSkippingChanges(): Promise<{ hasSkippedChangesEnabled: boolean, hasSkippedCustomizations: boolean }> { + if (!this.skipChanges || !await this.gitService.hasCustomBranch(this.sdkWithSourceTree)) { + return { hasSkippedChangesEnabled: this.skipChanges, hasSkippedCustomizations: false }; + } + await Promise.all(this.gitService.getMergeFiles(this.sdkWithSourceTree).map((filePath) => this.fileService.deleteFile(filePath))); + await this.gitService.forceCheckoutMainBranch(this.sdkWithSourceTree); + await this.fileService.deleteDirectory(this.sdkWithSourceTree.join(".git")); + return { hasSkippedChangesEnabled: true, hasSkippedCustomizations: true }; + } + + public async saveWithoutConflicts(): Promise<{ hasSourceTreeTracked: boolean, hasSourceTreeAlreadyTracked: boolean, hasAppliedCustomizations: boolean }> { + if (await this.fileService.fileExists(this.gitService.getMergeFiles(this.sdkWithSourceTree)[0])) { + return { hasSourceTreeTracked: false, hasSourceTreeAlreadyTracked: false, hasAppliedCustomizations: false }; + } + + if (this.trackChanges || this.hasSdkSourceTree) { + await this.saveSourceTree(); + } + + return { + hasSourceTreeTracked: this.trackChanges || this.hasSdkSourceTree, + hasSourceTreeAlreadyTracked: this.trackChanges && this.hasSdkSourceTree, + hasAppliedCustomizations: await this.gitService.hasCustomBranch(this.sdkWithSourceTree) + }; + } + + public async saveWithResolvedConflicts(): Promise { + await this.gitService.commitResolvedConflicts(this.sdkWithSourceTree); + for (const filePath of this.gitService.getMergeFiles(this.sdkWithSourceTree)) { + await this.fileService.deleteFile(filePath); + } + await this.saveSourceTree(); + + // Re create the sdkWithoutSourceTree using the updated resolved state of the sdkWithSourceTree + await this.fileService.cleanDirectory(this.sdkWithoutSourceTree); + await this.fileService.copyDirectoryExcluding(this.sdkWithSourceTree, this.sdkWithoutSourceTree, [new FileName(".git")]); + } + + private async saveSourceTree(): Promise { + await this.fileService.ensurePathExists(this.sourceTreePath); + await this.gitService.forceCheckoutMainBranch(this.sdkWithSourceTree); + await this.zipService.archive(this.sdkWithSourceTree.join(".git"), this.sourceTreePath); + } + + public async getConflictedFilesDirectory(): Promise { + const updatedFilesDirectory = await this.gitService.getDirectoryWithUpdatedFiles(this.sdkWithSourceTree); + const conflictMarker = this.gitService.getConflictMarker(); + return updatedFilesDirectory.mapFilesInDirectory(async (directoryPath, fileItem) => { + if (await this.fileService.hasContent(new FilePath(directoryPath, fileItem.fileName), conflictMarker)) { + return { fileName: fileItem.fileName, description: "# Conflicted file" }; + } + return undefined; + }); + } + + public async cleanUp(onCleanUpFailure: () => Promise): Promise { + await this.fileService.pollDeleteDirectory(this.sdkWithSourceTree, onCleanUpFailure); + } +} diff --git a/src/types/save-changes-context.ts b/src/types/save-changes-context.ts new file mode 100644 index 00000000..111a65ae --- /dev/null +++ b/src/types/save-changes-context.ts @@ -0,0 +1,59 @@ +import { FileService } from "../infrastructure/file-service.js"; +import { GitService } from "../infrastructure/git-service.js"; +import { ZipService } from "../infrastructure/zip-service.js"; +import { Directory } from "./file/directory.js"; +import { DirectoryPath } from "./file/directoryPath.js"; +import { FileName } from "./file/fileName.js"; +import { FilePath } from "./file/filePath.js"; +import { Language } from "./sdk/generate.js"; + +export class SaveChangesContext { + private readonly fileService = new FileService(); + private readonly zipService = new ZipService(); + private readonly gitService = new GitService(); + private readonly sdkInputDirectory: DirectoryPath; + + constructor( + private readonly sourceTreePath: FilePath, + private readonly sdkReviewDirectory: DirectoryPath, + sdkInputDirectory: DirectoryPath | undefined, + workingDirectory: DirectoryPath, + language: Language, + version: string | undefined + ) { + this.sdkInputDirectory = sdkInputDirectory ?? (version + ? workingDirectory.join("sdk").join(version).join(language) + : workingDirectory.join("sdk").join(language)); + } + + public async isSdkInputDirectoryMissing(onMissingDirectory: (directory: DirectoryPath) => void) { + if (await this.fileService.directoryEmpty(this.sdkInputDirectory)) { + onMissingDirectory(this.sdkInputDirectory); + return true; + } + return false; + } + + public async getChangesForReviewDirectory(): Promise { + const sdkGitDir = this.sdkReviewDirectory.join(".git"); + await this.fileService.createDirectoryIfNotExists(sdkGitDir); + await this.zipService.unArchive(this.sourceTreePath, sdkGitDir); + + await this.gitService.checkoutCustomBranch(this.sdkReviewDirectory); + await this.fileService.cleanDirectoryExcluding(this.sdkReviewDirectory, [new FileName(".git")]); + await this.fileService.copyDirectoryExcluding(this.sdkInputDirectory, this.sdkReviewDirectory, [new FileName(".git")]); + + return this.gitService.getDirectoryWithUpdatedFiles(this.sdkReviewDirectory); + } + + public async cleanUpSdkReviewDirectory(onCleanUpFailure: () => void): Promise { + await this.fileService.pollDeleteDirectory(this.sdkReviewDirectory, onCleanUpFailure); + } + + public async saveSourceTree(): Promise { + const sdkGitDir = this.sdkReviewDirectory.join(".git"); + await this.gitService.commitReviewedChanges(this.sdkReviewDirectory); + await this.gitService.forceCheckoutMainBranch(this.sdkReviewDirectory); + await this.zipService.archive(sdkGitDir, this.sourceTreePath); + } +} diff --git a/src/types/sdk-context.ts b/src/types/sdk-context.ts index 8d613102..64c4828b 100644 --- a/src/types/sdk-context.ts +++ b/src/types/sdk-context.ts @@ -2,35 +2,61 @@ import { FileService } from "../infrastructure/file-service.js"; import { DirectoryPath } from "./file/directoryPath.js"; import { FilePath } from "./file/filePath.js"; import { FileName } from "./file/fileName.js"; -import { ZipService } from "../infrastructure/zip-service.js"; import { Language } from "./sdk/generate.js"; +import { ZipService } from "../infrastructure/zip-service.js"; export class SdkContext { private readonly fileService = new FileService(); private readonly zipService = new ZipService(); + private readonly sdkDirectory: DirectoryPath; - constructor(private readonly sdkDirectory: DirectoryPath, private readonly language: Language) { + constructor( + private readonly language: Language, + sdkDirectory: DirectoryPath, + requireUncustomizedDir: boolean, + version?: string + ) { + const baseDirectory = requireUncustomizedDir ? sdkDirectory.join('uncustomized') : sdkDirectory; + + this.sdkDirectory = version ? baseDirectory.join(version).join(language) : baseDirectory.join(language); } private get zipPath(): FilePath { - return new FilePath(this.sdkLanguageDirectory, new FileName(`${this.language}.zip`)); - } - - public get sdkLanguageDirectory(): DirectoryPath { - return this.sdkDirectory.join(this.language); + return new FilePath(this.sdkDirectory, new FileName(`${this.language}.zip`)); } public async exists() { - return !(await this.fileService.directoryEmpty(this.sdkLanguageDirectory)); + return !(await this.fileService.directoryEmpty(this.sdkDirectory)); } - public async save(tempPortalFilePath: FilePath, zipPortal: boolean) { - await this.fileService.cleanDirectory(this.sdkLanguageDirectory); - if (zipPortal) { - await this.fileService.copy(tempPortalFilePath, this.zipPath); + public async save(tempSdkDirectory: DirectoryPath, zipSdk: boolean): Promise { + await this.fileService.cleanDirectory(this.sdkDirectory); + if (!zipSdk) { + await this.fileService.copyDirectoryContents(tempSdkDirectory, this.sdkDirectory); } else { - await this.zipService.unArchive(tempPortalFilePath, this.sdkLanguageDirectory); + await this.zipService.archive(tempSdkDirectory, this.zipPath); } - return this.sdkLanguageDirectory; + return this.sdkDirectory; + } + + public async loadSdkInTempDirectory(tempDirectory: DirectoryPath, tempSdk: FilePath): Promise { + const tempSdkDirectory = tempDirectory.join('sdk-original'); + await this.fileService.createDirectoryIfNotExists(tempSdkDirectory); + await this.zipService.unArchive(tempSdk, tempSdkDirectory); + return tempSdkDirectory; + } + + public async loadSdkWithSourceTreeInTempDirectory( + tempDirectory: DirectoryPath, + tempSdk: FilePath, + tempSdkSourceTree: FilePath + ): Promise { + const tempSdkDirectory = tempDirectory.join('sdk'); + await this.fileService.createDirectoryIfNotExists(tempSdkDirectory); + await this.zipService.unArchive(tempSdk, tempSdkDirectory); + const gitSourceTreeDir = tempSdkDirectory.join('.git'); + await this.fileService.createDirectoryIfNotExists(gitSourceTreeDir); + await this.zipService.unArchive(tempSdkSourceTree, gitSourceTreeDir); + return tempSdkDirectory; } } diff --git a/src/types/spec-context.ts b/src/types/spec-context.ts index be038b03..443134a4 100644 --- a/src/types/spec-context.ts +++ b/src/types/spec-context.ts @@ -9,6 +9,7 @@ export class SpecContext { private readonly zipService = new ZipService(); private readonly specDirectory: DirectoryPath; + constructor(specDirectory: DirectoryPath) { this.specDirectory = specDirectory; } diff --git a/src/types/temp-context.ts b/src/types/temp-context.ts index 975c72b4..bf859db0 100644 --- a/src/types/temp-context.ts +++ b/src/types/temp-context.ts @@ -9,7 +9,7 @@ export class TempContext { private readonly fileService = new FileService(); private readonly zipService = new ZipService(); - constructor(private readonly tempDirectory: DirectoryPath) {} + constructor(private readonly tempDirectory: DirectoryPath) { } private get getTempFileName(): FilePath { const uuid = randomUUID(); diff --git a/src/types/versioned-build-context.ts b/src/types/versioned-build-context.ts deleted file mode 100644 index 266f3245..00000000 --- a/src/types/versioned-build-context.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { FileService } from "../infrastructure/file-service.js"; -import { DirectoryPath } from "./file/directoryPath.js"; -import { BuildContext } from "./build-context.js"; - -type VersionedBuildValidateResult = { isValid: false; } | { - isValid: true; - versionsDirectory: DirectoryPath; - versions: string[]; -}; - -export class VersionedBuildContext { - private readonly fileService = new FileService(); - private readonly buildDirectory: DirectoryPath; - - constructor(buildDirectory: DirectoryPath) { - this.buildDirectory = buildDirectory; - } - - public async validate(): Promise { - const buildContext = new BuildContext(this.buildDirectory); - if (!(await buildContext.validate())) { - return { isValid: false }; - } - const config = await buildContext.getBuildFileContents(); - if (!("generateVersionedPortal" in config)) { - return { isValid: false }; - } - const versionsPath = (config.versionsPath as string) ?? "versioned_docs"; - const versionsDirectory = this.buildDirectory.join(versionsPath); - if (!(await this.fileService.directoryExists(versionsDirectory))) { - return { isValid: false }; - } - const versionsDirs = await this.fileService.getSubDirectoriesPaths(versionsDirectory); - const versions = versionsDirs.map((dir) => dir.leafName()); - return { isValid: true, versionsDirectory, versions }; - } -} From ba3a665183fa6c4f11d33ef3d2deba30bef179ba Mon Sep 17 00:00:00 2001 From: Ayesha <88117894+Ayeshas09@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:45:04 +0500 Subject: [PATCH 2/2] fix: update readme and applies prompts improvements (#268) What was added? - Updates the ReadMe for `sdk save-changes` command - Adds the messaging improvements for `sdk generate` and `sdk save-changes` -------------------------------------------------------------- Co-authored-by: Asad Ali <14asadali@gmail.com> Co-authored-by: Asad Ali --- README.md | 46 ++++++++++-- package-lock.json | 12 ++-- package.json | 7 +- src/actions/sdk/generate.ts | 2 +- src/actions/sdk/merge-source-tree.ts | 38 ++++++---- src/actions/sdk/save-changes.ts | 41 ++++++----- src/commands/sdk/generate.ts | 2 +- src/infrastructure/file-service.ts | 4 -- src/infrastructure/git-service.ts | 71 +++++++------------ src/infrastructure/launcher-service.ts | 10 +++ src/prompts/portal/generate.ts | 10 +-- src/prompts/sdk/generate.ts | 3 +- src/prompts/sdk/merge-source-tree.ts | 97 ++++++++++++++++---------- src/prompts/sdk/save-changes.ts | 47 +++++++------ src/types/build-context.ts | 7 ++ src/types/file/directory.ts | 38 ++++++++++ src/types/merge-source-tree-context.ts | 20 ++---- 17 files changed, 278 insertions(+), 177 deletions(-) diff --git a/README.md b/README.md index f4edadaf..93e3a05b 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ USAGE * [`apimatic portal toc new`](#apimatic-portal-toc-new) * [`apimatic quickstart`](#apimatic-quickstart) * [`apimatic sdk generate`](#apimatic-sdk-generate) +* [`apimatic sdk save-changes`](#apimatic-sdk-save-changes) ## `apimatic api transform` @@ -411,18 +412,21 @@ Generate an SDK for your API ``` USAGE - $ apimatic sdk generate -l csharp|java|php|python|ruby|typescript|go [-i ] [-d ] [--api-version - ] [-f] [--zip] [-k ] + $ apimatic sdk generate -l csharp|java|php|python|ruby|typescript|go [-d ] [--skip-changes] + [--api-version ] [--zip] [--track-changes] [-i ] [-f] [-k ] FLAGS - -d, --destination= directory where the SDK will be generated + -d, --destination= [default: /sdk/ | /sdk//] path where the SDK + will be generated -f, --force overwrite changes without asking for user consent. -i, --input= [default: ./] path to the parent directory containing the 'src' directory, which includes API specifications and configuration files. -k, --auth-key= override current authentication state with an authentication key. - -l, --language=