@uapkg/cli 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/app/CompositionRoot.js.map +1 -1
  2. package/dist/app/UAPKGApplication.js.map +1 -1
  3. package/dist/build/BuildMetadata.js +2 -2
  4. package/dist/cli/parseCommandLine.js.map +1 -1
  5. package/dist/cli/parsing/UAPKGCommandLineParser.js.map +1 -1
  6. package/dist/cli/parsing/UAPKGParserRegistry.js.map +1 -1
  7. package/dist/cli/parsing/modules/AddCommandModule.js +4 -4
  8. package/dist/cli/parsing/modules/AddCommandModule.js.map +1 -1
  9. package/dist/cli/parsing/modules/ConfigCommandModule.js +2 -2
  10. package/dist/cli/parsing/modules/ConfigCommandModule.js.map +1 -1
  11. package/dist/cli/parsing/modules/InitCommandModule.js +2 -2
  12. package/dist/cli/parsing/modules/InitCommandModule.js.map +1 -1
  13. package/dist/cli/parsing/modules/InstallCommandModule.js +3 -3
  14. package/dist/cli/parsing/modules/InstallCommandModule.js.map +1 -1
  15. package/dist/cli/parsing/modules/LifecycleCommandModule.js.map +1 -1
  16. package/dist/cli/parsing/modules/ListCommandModule.js +1 -1
  17. package/dist/cli/parsing/modules/ListCommandModule.js.map +1 -1
  18. package/dist/cli/parsing/modules/LoginCommandModule.js +2 -2
  19. package/dist/cli/parsing/modules/LoginCommandModule.js.map +1 -1
  20. package/dist/cli/parsing/modules/LogoutCommandModule.js.map +1 -1
  21. package/dist/cli/parsing/modules/OutdatedCommandModule.js.map +1 -1
  22. package/dist/cli/parsing/modules/PackCommandModule.js +2 -2
  23. package/dist/cli/parsing/modules/PackCommandModule.js.map +1 -1
  24. package/dist/cli/parsing/modules/ProjectGetNameCommandModule.js.map +1 -1
  25. package/dist/cli/parsing/modules/PublishCommandModule.js +3 -3
  26. package/dist/cli/parsing/modules/PublishCommandModule.js.map +1 -1
  27. package/dist/cli/parsing/modules/RegistryCommandModule.js.map +1 -1
  28. package/dist/cli/parsing/modules/RemoveCommandModule.js +1 -1
  29. package/dist/cli/parsing/modules/RemoveCommandModule.js.map +1 -1
  30. package/dist/cli/parsing/modules/RequestsCommandModule.js +1 -1
  31. package/dist/cli/parsing/modules/RequestsCommandModule.js.map +1 -1
  32. package/dist/cli/parsing/modules/UpdateCommandModule.js +3 -3
  33. package/dist/cli/parsing/modules/UpdateCommandModule.js.map +1 -1
  34. package/dist/cli/parsing/modules/WhoamiCommandModule.d.ts.map +1 -1
  35. package/dist/cli/parsing/modules/WhoamiCommandModule.js.map +1 -1
  36. package/dist/cli/parsing/modules/WhyCommandModule.js +1 -1
  37. package/dist/cli/parsing/modules/WhyCommandModule.js.map +1 -1
  38. package/dist/cli/runUAPKGCLI.js.map +1 -1
  39. package/dist/cli-bootstrap.js.map +1 -1
  40. package/dist/commands/AddCommand.js.map +1 -1
  41. package/dist/commands/ConfigCommand.js.map +1 -1
  42. package/dist/commands/InitCommand.d.ts.map +1 -1
  43. package/dist/commands/InitCommand.js +2 -2
  44. package/dist/commands/InitCommand.js.map +1 -1
  45. package/dist/commands/InstallCommand.js.map +1 -1
  46. package/dist/commands/ListCommand.js.map +1 -1
  47. package/dist/commands/LoginCommand.js.map +1 -1
  48. package/dist/commands/LogoutCommand.js.map +1 -1
  49. package/dist/commands/OutdatedCommand.js.map +1 -1
  50. package/dist/commands/PackCommand.js.map +1 -1
  51. package/dist/commands/PackageLifecycleCommand.js.map +1 -1
  52. package/dist/commands/PostinstallCandidateBuilder.js.map +1 -1
  53. package/dist/commands/ProjectGetNameCommand.js.map +1 -1
  54. package/dist/commands/PublishCommand.js.map +1 -1
  55. package/dist/commands/RegistryCommand.js.map +1 -1
  56. package/dist/commands/RemoveCommand.js.map +1 -1
  57. package/dist/commands/RequestsCommand.js.map +1 -1
  58. package/dist/commands/UpdateCommand.js.map +1 -1
  59. package/dist/commands/WhoamiCommand.js.map +1 -1
  60. package/dist/commands/WhyCommand.js.map +1 -1
  61. package/dist/commands/add/DependencyAddResolver.js.map +1 -1
  62. package/dist/control-plane/AccountManager.js +1 -1
  63. package/dist/control-plane/AccountManager.js.map +1 -1
  64. package/dist/control-plane/AuthMetadataStore.js.map +1 -1
  65. package/dist/control-plane/AuthenticationSelector.js +5 -5
  66. package/dist/control-plane/AuthenticationSelector.js.map +1 -1
  67. package/dist/control-plane/ControlPlaneClient.js.map +1 -1
  68. package/dist/control-plane/ControlPlaneEndpoints.js.map +1 -1
  69. package/dist/control-plane/ControlPlaneTypes.js.map +1 -1
  70. package/dist/control-plane/DPoPKeyStore.js.map +1 -1
  71. package/dist/control-plane/GitHubActionsOidcCredentialProvider.js.map +1 -1
  72. package/dist/control-plane/PublishRequestErrorMapper.js.map +1 -1
  73. package/dist/control-plane/RegistryTrustResolver.js.map +1 -1
  74. package/dist/postinstall/api/definePostinstall.js.map +1 -1
  75. package/dist/postinstall/loader/ExportValidator.js.map +1 -1
  76. package/dist/postinstall/loader/PostinstallLoader.js.map +1 -1
  77. package/dist/postinstall/markers/MarkerBlockEditor.js.map +1 -1
  78. package/dist/postinstall/runner/PostinstallOrchestrator.js.map +1 -1
  79. package/dist/postinstall/unreal/BuildCsInjector.js.map +1 -1
  80. package/dist/postinstall/unreal/CSharpInjectionEngine.js.map +1 -1
  81. package/dist/postinstall/unreal/CSharpStructureAnalyzer.d.ts.map +1 -1
  82. package/dist/postinstall/unreal/CSharpStructureAnalyzer.js.map +1 -1
  83. package/dist/postinstall/unreal/TargetCsInjector.js.map +1 -1
  84. package/dist/postinstall/unreal/UnrealSourceCatalog.js.map +1 -1
  85. package/dist/prompts/InkPromptService.js.map +1 -1
  86. package/dist/reporting/DiagnosticReporter.js.map +1 -1
  87. package/dist/reporting/InkDiagnosticRenderer.js.map +1 -1
  88. package/dist/reporting/InstallProgressReporter.js.map +1 -1
  89. package/dist/reporting/JsonReporter.js.map +1 -1
  90. package/dist/reporting/RegistryRequestTerminalFormatter.js +1 -1
  91. package/dist/reporting/RegistryRequestTerminalFormatter.js.map +1 -1
  92. package/dist/reporting/TextDiagnosticRenderer.js.map +1 -1
  93. package/package.json +26 -14
@@ -1 +1 @@
1
- {"version":3,"file":"ExportValidator.js","names":[],"sources":["../../../src/postinstall/loader/ExportValidator.ts"],"sourcesContent":["import { createPostinstallInvalidExportDiagnostic, fail, ok, type Result } from '@uapkg/diagnostics';\nimport { type PostinstallDefinition, PostinstallDefinitionSchema } from '../api/PostinstallDsl.js';\n\n/**\n * Normalizes an imported module to its default export (or the module object\n * itself for CommonJS-style returns) and validates against\n * {@link PostinstallDefinitionSchema}.\n *\n * Emits `POSTINSTALL_INVALID_EXPORT` with flattened Zod issue messages when\n * the shape is wrong.\n */\nexport class ExportValidator {\n public validate(packageName: string, entryFile: string, moduleValue: unknown): Result<PostinstallDefinition> {\n const unwrapped = this.unwrap(moduleValue);\n const parsed = PostinstallDefinitionSchema.safeParse(unwrapped);\n if (!parsed.success) {\n const issues = parsed.error.issues.map((issue) => {\n const where = issue.path.length > 0 ? ` at ${issue.path.join('.')}` : '';\n return `${issue.message}${where}`;\n });\n return fail([createPostinstallInvalidExportDiagnostic(packageName, entryFile, issues)]);\n }\n return ok(parsed.data);\n }\n\n private unwrap(moduleValue: unknown): unknown {\n if (moduleValue && typeof moduleValue === 'object') {\n const record = moduleValue as Record<string, unknown>;\n if ('default' in record) return record.default;\n return record;\n }\n return moduleValue;\n }\n}\n"],"mappings":";;;;;;;;;;;;AAWA,IAAa,kBAAb,MAA6B;CAC3B,AAAO,SAAS,aAAqB,WAAmB,aAAqD;EAC3G,MAAM,YAAY,KAAK,OAAO,WAAW;EACzC,MAAM,SAAS,4BAA4B,UAAU,SAAS;EAC9D,IAAI,CAAC,OAAO,SAAS;GACnB,MAAM,SAAS,OAAO,MAAM,OAAO,KAAK,UAAU;IAChD,MAAM,QAAQ,MAAM,KAAK,SAAS,IAAI,OAAO,MAAM,KAAK,KAAK,GAAG,MAAM;IACtE,OAAO,GAAG,MAAM,UAAU;GAC5B,CAAC;GACD,OAAO,KAAK,CAAC,yCAAyC,aAAa,WAAW,MAAM,CAAC,CAAC;EACxF;EACA,OAAO,GAAG,OAAO,IAAI;CACvB;CAEA,AAAQ,OAAO,aAA+B;EAC5C,IAAI,eAAe,OAAO,gBAAgB,UAAU;GAClD,MAAM,SAAS;GACf,IAAI,aAAa,QAAQ,OAAO,OAAO;GACvC,OAAO;EACT;EACA,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"ExportValidator.js","names":[],"sources":["../../../src/postinstall/loader/ExportValidator.ts"],"sourcesContent":["import { createPostinstallInvalidExportDiagnostic, fail, ok, type Result } from '@uapkg/diagnostics';\nimport { type PostinstallDefinition, PostinstallDefinitionSchema } from '#cli/postinstall/api/PostinstallDsl.ts';\n\n/**\n * Normalizes an imported module to its default export (or the module object\n * itself for CommonJS-style returns) and validates against\n * {@link PostinstallDefinitionSchema}.\n *\n * Emits `POSTINSTALL_INVALID_EXPORT` with flattened Zod issue messages when\n * the shape is wrong.\n */\nexport class ExportValidator {\n public validate(packageName: string, entryFile: string, moduleValue: unknown): Result<PostinstallDefinition> {\n const unwrapped = this.unwrap(moduleValue);\n const parsed = PostinstallDefinitionSchema.safeParse(unwrapped);\n if (!parsed.success) {\n const issues = parsed.error.issues.map((issue) => {\n const where = issue.path.length > 0 ? ` at ${issue.path.join('.')}` : '';\n return `${issue.message}${where}`;\n });\n return fail([createPostinstallInvalidExportDiagnostic(packageName, entryFile, issues)]);\n }\n return ok(parsed.data);\n }\n\n private unwrap(moduleValue: unknown): unknown {\n if (moduleValue && typeof moduleValue === 'object') {\n const record = moduleValue as Record<string, unknown>;\n if ('default' in record) return record.default;\n return record;\n }\n return moduleValue;\n }\n}\n"],"mappings":";;;;;;;;;;;;AAWA,IAAa,kBAAb,MAA6B;CAC3B,AAAO,SAAS,aAAqB,WAAmB,aAAqD;EAC3G,MAAM,YAAY,KAAK,OAAO,WAAW;EACzC,MAAM,SAAS,4BAA4B,UAAU,SAAS;EAC9D,IAAI,CAAC,OAAO,SAAS;GACnB,MAAM,SAAS,OAAO,MAAM,OAAO,KAAK,UAAU;IAChD,MAAM,QAAQ,MAAM,KAAK,SAAS,IAAI,OAAO,MAAM,KAAK,KAAK,GAAG,MAAM;IACtE,OAAO,GAAG,MAAM,UAAU;GAC5B,CAAC;GACD,OAAO,KAAK,CAAC,yCAAyC,aAAa,WAAW,MAAM,CAAC,CAAC;EACxF;EACA,OAAO,GAAG,OAAO,IAAI;CACvB;CAEA,AAAQ,OAAO,aAA+B;EAC5C,IAAI,eAAe,OAAO,gBAAgB,UAAU;GAClD,MAAM,SAAS;GACf,IAAI,aAAa,QAAQ,OAAO,OAAO;GACvC,OAAO;EACT;EACA,OAAO;CACT;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"PostinstallLoader.js","names":[],"sources":["../../../src/postinstall/loader/PostinstallLoader.ts"],"sourcesContent":["import { fail, ok, type Result } from '@uapkg/diagnostics';\nimport type { PostinstallDefinition } from '../api/PostinstallDsl.js';\nimport { EntryResolver, type ResolvedEntry } from './EntryResolver.js';\nimport { EsbuildTranspiler } from './EsbuildTranspiler.js';\nimport { ExportValidator } from './ExportValidator.js';\nimport { ModuleImporter, type ModuleSource } from './ModuleImporter.js';\n\nexport interface LoadedPostinstall {\n readonly packageName: string;\n readonly pluginRoot: string;\n readonly entryFile: string;\n readonly definition: PostinstallDefinition;\n}\n\n/**\n * Facade that composes the four loader stages:\n * 1. {@link EntryResolver} locate `.uapkg/postinstall.<ext>`\n * 2. {@link EsbuildTranspiler} if `.ts`, transpile to ESM\n * 3. {@link ModuleImporter} dynamic import → raw module value\n * 4. {@link ExportValidator} Zod-validate default export\n *\n * Returns `ok(null)` when a plugin has no postinstall — a valid, silent state.\n * Never throws: every stage returns `Result`.\n */\nexport class PostinstallLoader {\n public constructor(\n private readonly entryResolver: EntryResolver = new EntryResolver(),\n private readonly transpiler: EsbuildTranspiler = new EsbuildTranspiler(),\n private readonly importer: ModuleImporter = new ModuleImporter(),\n private readonly validator: ExportValidator = new ExportValidator(),\n ) {}\n\n public async load(packageName: string, pluginRoot: string): Promise<Result<LoadedPostinstall | null>> {\n const entryResult = this.entryResolver.resolve(packageName, pluginRoot);\n if (!entryResult.ok) return entryResult;\n const entry = entryResult.value;\n if (entry === null) return ok(null);\n\n const sourceResult = await this.buildModuleSource(packageName, entry);\n if (!sourceResult.ok) return sourceResult;\n\n const importResult = await this.importer.import(packageName, sourceResult.value);\n if (!importResult.ok) return importResult;\n\n const validated = this.validator.validate(packageName, entry.path, importResult.value);\n if (!validated.ok) return validated;\n\n return ok({\n packageName,\n pluginRoot,\n entryFile: entry.path,\n definition: validated.value,\n });\n }\n\n private async buildModuleSource(packageName: string, entry: ResolvedEntry): Promise<Result<ModuleSource>> {\n if (entry.kind !== 'ts') {\n return ok({ kind: 'file', path: entry.path });\n }\n const transpiled = await this.transpiler.transpile(packageName, entry.path);\n if (!transpiled.ok) return fail(transpiled.diagnostics);\n return ok({ kind: 'transpiled', code: transpiled.value, originalPath: entry.path });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAwBA,IAAa,oBAAb,MAA+B;CAEV;CACA;CACA;CACA;CAJnB,AAAO,YACL,AAAiB,gBAA+B,IAAI,cAAc,GAClE,AAAiB,aAAgC,IAAI,kBAAkB,GACvE,AAAiB,WAA2B,IAAI,eAAe,GAC/D,AAAiB,YAA6B,IAAI,gBAAgB,GAClE;EAJiB;EACA;EACA;EACA;CAChB;CAEH,MAAa,KAAK,aAAqB,YAA+D;EACpG,MAAM,cAAc,KAAK,cAAc,QAAQ,aAAa,UAAU;EACtE,IAAI,CAAC,YAAY,IAAI,OAAO;EAC5B,MAAM,QAAQ,YAAY;EAC1B,IAAI,UAAU,MAAM,OAAO,GAAG,IAAI;EAElC,MAAM,eAAe,MAAM,KAAK,kBAAkB,aAAa,KAAK;EACpE,IAAI,CAAC,aAAa,IAAI,OAAO;EAE7B,MAAM,eAAe,MAAM,KAAK,SAAS,OAAO,aAAa,aAAa,KAAK;EAC/E,IAAI,CAAC,aAAa,IAAI,OAAO;EAE7B,MAAM,YAAY,KAAK,UAAU,SAAS,aAAa,MAAM,MAAM,aAAa,KAAK;EACrF,IAAI,CAAC,UAAU,IAAI,OAAO;EAE1B,OAAO,GAAG;GACR;GACA;GACA,WAAW,MAAM;GACjB,YAAY,UAAU;EACxB,CAAC;CACH;CAEA,MAAc,kBAAkB,aAAqB,OAAqD;EACxG,IAAI,MAAM,SAAS,MACjB,OAAO,GAAG;GAAE,MAAM;GAAQ,MAAM,MAAM;EAAK,CAAC;EAE9C,MAAM,aAAa,MAAM,KAAK,WAAW,UAAU,aAAa,MAAM,IAAI;EAC1E,IAAI,CAAC,WAAW,IAAI,OAAO,KAAK,WAAW,WAAW;EACtD,OAAO,GAAG;GAAE,MAAM;GAAc,MAAM,WAAW;GAAO,cAAc,MAAM;EAAK,CAAC;CACpF;AACF"}
1
+ {"version":3,"file":"PostinstallLoader.js","names":[],"sources":["../../../src/postinstall/loader/PostinstallLoader.ts"],"sourcesContent":["import { fail, ok, type Result } from '@uapkg/diagnostics';\nimport type { PostinstallDefinition } from '#cli/postinstall/api/PostinstallDsl.ts';\nimport { EntryResolver, type ResolvedEntry } from '#cli/postinstall/loader/EntryResolver.ts';\nimport { EsbuildTranspiler } from '#cli/postinstall/loader/EsbuildTranspiler.ts';\nimport { ExportValidator } from '#cli/postinstall/loader/ExportValidator.ts';\nimport { ModuleImporter, type ModuleSource } from '#cli/postinstall/loader/ModuleImporter.ts';\n\nexport interface LoadedPostinstall {\n readonly packageName: string;\n readonly pluginRoot: string;\n readonly entryFile: string;\n readonly definition: PostinstallDefinition;\n}\n\n/**\n * Facade that composes the four loader stages:\n * 1. {@link EntryResolver} locate `.uapkg/postinstall.<ext>`\n * 2. {@link EsbuildTranspiler} if `.ts`, transpile to ESM\n * 3. {@link ModuleImporter} dynamic import → raw module value\n * 4. {@link ExportValidator} Zod-validate default export\n *\n * Returns `ok(null)` when a plugin has no postinstall — a valid, silent state.\n * Never throws: every stage returns `Result`.\n */\nexport class PostinstallLoader {\n public constructor(\n private readonly entryResolver: EntryResolver = new EntryResolver(),\n private readonly transpiler: EsbuildTranspiler = new EsbuildTranspiler(),\n private readonly importer: ModuleImporter = new ModuleImporter(),\n private readonly validator: ExportValidator = new ExportValidator(),\n ) {}\n\n public async load(packageName: string, pluginRoot: string): Promise<Result<LoadedPostinstall | null>> {\n const entryResult = this.entryResolver.resolve(packageName, pluginRoot);\n if (!entryResult.ok) return entryResult;\n const entry = entryResult.value;\n if (entry === null) return ok(null);\n\n const sourceResult = await this.buildModuleSource(packageName, entry);\n if (!sourceResult.ok) return sourceResult;\n\n const importResult = await this.importer.import(packageName, sourceResult.value);\n if (!importResult.ok) return importResult;\n\n const validated = this.validator.validate(packageName, entry.path, importResult.value);\n if (!validated.ok) return validated;\n\n return ok({\n packageName,\n pluginRoot,\n entryFile: entry.path,\n definition: validated.value,\n });\n }\n\n private async buildModuleSource(packageName: string, entry: ResolvedEntry): Promise<Result<ModuleSource>> {\n if (entry.kind !== 'ts') {\n return ok({ kind: 'file', path: entry.path });\n }\n const transpiled = await this.transpiler.transpile(packageName, entry.path);\n if (!transpiled.ok) return fail(transpiled.diagnostics);\n return ok({ kind: 'transpiled', code: transpiled.value, originalPath: entry.path });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAwBA,IAAa,oBAAb,MAA+B;CAEV;CACA;CACA;CACA;CAJnB,AAAO,YACL,AAAiB,gBAA+B,IAAI,cAAc,GAClE,AAAiB,aAAgC,IAAI,kBAAkB,GACvE,AAAiB,WAA2B,IAAI,eAAe,GAC/D,AAAiB,YAA6B,IAAI,gBAAgB,GAClE;EAJiB;EACA;EACA;EACA;CAChB;CAEH,MAAa,KAAK,aAAqB,YAA+D;EACpG,MAAM,cAAc,KAAK,cAAc,QAAQ,aAAa,UAAU;EACtE,IAAI,CAAC,YAAY,IAAI,OAAO;EAC5B,MAAM,QAAQ,YAAY;EAC1B,IAAI,UAAU,MAAM,OAAO,GAAG,IAAI;EAElC,MAAM,eAAe,MAAM,KAAK,kBAAkB,aAAa,KAAK;EACpE,IAAI,CAAC,aAAa,IAAI,OAAO;EAE7B,MAAM,eAAe,MAAM,KAAK,SAAS,OAAO,aAAa,aAAa,KAAK;EAC/E,IAAI,CAAC,aAAa,IAAI,OAAO;EAE7B,MAAM,YAAY,KAAK,UAAU,SAAS,aAAa,MAAM,MAAM,aAAa,KAAK;EACrF,IAAI,CAAC,UAAU,IAAI,OAAO;EAE1B,OAAO,GAAG;GACR;GACA;GACA,WAAW,MAAM;GACjB,YAAY,UAAU;EACxB,CAAC;CACH;CAEA,MAAc,kBAAkB,aAAqB,OAAqD;EACxG,IAAI,MAAM,SAAS,MACjB,OAAO,GAAG;GAAE,MAAM;GAAQ,MAAM,MAAM;EAAK,CAAC;EAE9C,MAAM,aAAa,MAAM,KAAK,WAAW,UAAU,aAAa,MAAM,IAAI;EAC1E,IAAI,CAAC,WAAW,IAAI,OAAO,KAAK,WAAW,WAAW;EACtD,OAAO,GAAG;GAAE,MAAM;GAAc,MAAM,WAAW;GAAO,cAAc,MAAM;EAAK,CAAC;CACpF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"MarkerBlockEditor.js","names":[],"sources":["../../../src/postinstall/markers/MarkerBlockEditor.ts"],"sourcesContent":["import { createPostinstallMarkerCorruptDiagnostic, fail, ok, type Result } from '@uapkg/diagnostics';\nimport { MarkerBlockService } from './MarkerBlockService.js';\nimport { MarkerIntegrityValidator } from './MarkerIntegrityValidator.js';\n\n/**\n * Higher-level idempotent wrapper around {@link MarkerBlockService}.\n *\n * - `upsert`: remove any existing owned block and re-insert fresh content.\n * Idempotent: calling twice with the same input yields the same result.\n * - `remove`: strip the owned block if present; no-op otherwise.\n * - `validate`: detect corruption (orphaned/nested markers) in the source for\n * the owning `(pluginName, zone)` pair. Returns a `POSTINSTALL_MARKER_CORRUPT`\n * diagnostic on failure; never throws.\n *\n * Injectors compose this helper to achieve \"edit once, safe to re-run\" semantics.\n */\nexport class MarkerBlockEditor {\n public constructor(\n private readonly service: MarkerBlockService = new MarkerBlockService(),\n private readonly integrity: MarkerIntegrityValidator = new MarkerIntegrityValidator(),\n ) {}\n\n public upsert(source: string, pluginName: string, zone: string, content: string, indent = ''): string {\n const stripped = this.service.stripOwnedBlock(source, pluginName, zone);\n const block = this.service.createBlock(pluginName, zone, content, indent);\n return `${stripped.trimEnd()}\\n${block}\\n`;\n }\n\n public remove(source: string, pluginName: string, zone: string): string {\n return this.service.stripOwnedBlock(source, pluginName, zone);\n }\n\n public contains(source: string, pluginName: string, zone: string): boolean {\n return this.service.containsOwnedBlock(source, pluginName, zone);\n }\n\n /**\n * Validates that the source contains a well-formed marker block for the\n * owning `(pluginName, zone)` pair. Returns `Result.ok` when clean; returns\n * `Result.fail` with a `POSTINSTALL_MARKER_CORRUPT` diagnostic otherwise.\n */\n public validate(source: string, pluginName: string, zone: string, filePath: string): Result<void> {\n const result = this.integrity.validate(source, pluginName, zone);\n if (result.ok) return ok(undefined);\n return fail([createPostinstallMarkerCorruptDiagnostic(pluginName, filePath, result.reason)]);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,IAAa,oBAAb,MAA+B;CAEV;CACA;CAFnB,AAAO,YACL,AAAiB,UAA8B,IAAI,mBAAmB,GACtE,AAAiB,YAAsC,IAAI,yBAAyB,GACpF;EAFiB;EACA;CAChB;CAEH,AAAO,OAAO,QAAgB,YAAoB,MAAc,SAAiB,SAAS,IAAY;EACpG,MAAM,WAAW,KAAK,QAAQ,gBAAgB,QAAQ,YAAY,IAAI;EACtE,MAAM,QAAQ,KAAK,QAAQ,YAAY,YAAY,MAAM,SAAS,MAAM;EACxE,OAAO,GAAG,SAAS,QAAQ,EAAE,IAAI,MAAM;CACzC;CAEA,AAAO,OAAO,QAAgB,YAAoB,MAAsB;EACtE,OAAO,KAAK,QAAQ,gBAAgB,QAAQ,YAAY,IAAI;CAC9D;CAEA,AAAO,SAAS,QAAgB,YAAoB,MAAuB;EACzE,OAAO,KAAK,QAAQ,mBAAmB,QAAQ,YAAY,IAAI;CACjE;;;;;;CAOA,AAAO,SAAS,QAAgB,YAAoB,MAAc,UAAgC;EAChG,MAAM,SAAS,KAAK,UAAU,SAAS,QAAQ,YAAY,IAAI;EAC/D,IAAI,OAAO,IAAI,OAAO,GAAG,MAAS;EAClC,OAAO,KAAK,CAAC,yCAAyC,YAAY,UAAU,OAAO,MAAM,CAAC,CAAC;CAC7F;AACF"}
1
+ {"version":3,"file":"MarkerBlockEditor.js","names":[],"sources":["../../../src/postinstall/markers/MarkerBlockEditor.ts"],"sourcesContent":["import { createPostinstallMarkerCorruptDiagnostic, fail, ok, type Result } from '@uapkg/diagnostics';\nimport { MarkerBlockService } from '#cli/postinstall/markers/MarkerBlockService.ts';\nimport { MarkerIntegrityValidator } from '#cli/postinstall/markers/MarkerIntegrityValidator.ts';\n\n/**\n * Higher-level idempotent wrapper around {@link MarkerBlockService}.\n *\n * - `upsert`: remove any existing owned block and re-insert fresh content.\n * Idempotent: calling twice with the same input yields the same result.\n * - `remove`: strip the owned block if present; no-op otherwise.\n * - `validate`: detect corruption (orphaned/nested markers) in the source for\n * the owning `(pluginName, zone)` pair. Returns a `POSTINSTALL_MARKER_CORRUPT`\n * diagnostic on failure; never throws.\n *\n * Injectors compose this helper to achieve \"edit once, safe to re-run\" semantics.\n */\nexport class MarkerBlockEditor {\n public constructor(\n private readonly service: MarkerBlockService = new MarkerBlockService(),\n private readonly integrity: MarkerIntegrityValidator = new MarkerIntegrityValidator(),\n ) {}\n\n public upsert(source: string, pluginName: string, zone: string, content: string, indent = ''): string {\n const stripped = this.service.stripOwnedBlock(source, pluginName, zone);\n const block = this.service.createBlock(pluginName, zone, content, indent);\n return `${stripped.trimEnd()}\\n${block}\\n`;\n }\n\n public remove(source: string, pluginName: string, zone: string): string {\n return this.service.stripOwnedBlock(source, pluginName, zone);\n }\n\n public contains(source: string, pluginName: string, zone: string): boolean {\n return this.service.containsOwnedBlock(source, pluginName, zone);\n }\n\n /**\n * Validates that the source contains a well-formed marker block for the\n * owning `(pluginName, zone)` pair. Returns `Result.ok` when clean; returns\n * `Result.fail` with a `POSTINSTALL_MARKER_CORRUPT` diagnostic otherwise.\n */\n public validate(source: string, pluginName: string, zone: string, filePath: string): Result<void> {\n const result = this.integrity.validate(source, pluginName, zone);\n if (result.ok) return ok(undefined);\n return fail([createPostinstallMarkerCorruptDiagnostic(pluginName, filePath, result.reason)]);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,IAAa,oBAAb,MAA+B;CAEV;CACA;CAFnB,AAAO,YACL,AAAiB,UAA8B,IAAI,mBAAmB,GACtE,AAAiB,YAAsC,IAAI,yBAAyB,GACpF;EAFiB;EACA;CAChB;CAEH,AAAO,OAAO,QAAgB,YAAoB,MAAc,SAAiB,SAAS,IAAY;EACpG,MAAM,WAAW,KAAK,QAAQ,gBAAgB,QAAQ,YAAY,IAAI;EACtE,MAAM,QAAQ,KAAK,QAAQ,YAAY,YAAY,MAAM,SAAS,MAAM;EACxE,OAAO,GAAG,SAAS,QAAQ,EAAE,IAAI,MAAM;CACzC;CAEA,AAAO,OAAO,QAAgB,YAAoB,MAAsB;EACtE,OAAO,KAAK,QAAQ,gBAAgB,QAAQ,YAAY,IAAI;CAC9D;CAEA,AAAO,SAAS,QAAgB,YAAoB,MAAuB;EACzE,OAAO,KAAK,QAAQ,mBAAmB,QAAQ,YAAY,IAAI;CACjE;;;;;;CAOA,AAAO,SAAS,QAAgB,YAAoB,MAAc,UAAgC;EAChG,MAAM,SAAS,KAAK,UAAU,SAAS,QAAQ,YAAY,IAAI;EAC/D,IAAI,OAAO,IAAI,OAAO,GAAG,MAAS;EAClC,OAAO,KAAK,CAAC,yCAAyC,YAAY,UAAU,OAAO,MAAM,CAAC,CAAC;CAC7F;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"PostinstallOrchestrator.js","names":[],"sources":["../../../src/postinstall/runner/PostinstallOrchestrator.ts"],"sourcesContent":["import type { Diagnostic, Result } from '@uapkg/diagnostics';\nimport { createIoErrorDiagnostic, createUnknownErrorDiagnostic, DiagnosticBag } from '@uapkg/diagnostics';\nimport type { PostinstallDefinition } from '../api/PostinstallDsl.js';\nimport { type LoadedPostinstall, PostinstallLoader } from '../loader/PostinstallLoader.js';\nimport { PostinstallPolicyGate } from '../policy/PostinstallPolicyGate.js';\nimport { BuildCsInjector } from '../unreal/BuildCsInjector.js';\nimport { ProjectFileLocator } from '../unreal/ProjectFileLocator.js';\nimport { TargetCsInjector } from '../unreal/TargetCsInjector.js';\nimport { UnrealSourceCatalogBuilder } from '../unreal/UnrealSourceCatalog.js';\nimport { UProjectInjector } from '../unreal/UProjectInjector.js';\nimport { UProjectMetadataReader } from '../unreal/UProjectMetadataReader.js';\n\n/**\n * Projection of an installer action that the orchestrator needs. The CLI\n * (Phase 8) will map `InstallAction` → `PostinstallCandidate` by filtering\n * `type === 'add' | 'update'` and joining with the lockfile for registry\n * metadata.\n */\nexport interface PostinstallCandidate {\n readonly packageName: string;\n readonly registry: string;\n /** Absolute path to the installed plugin root. */\n readonly pluginRoot: string;\n}\n\nexport interface PostinstallReport {\n readonly executed: readonly string[];\n readonly skipped: readonly { packageName: string; reason: 'no-script' | 'policy-denied' }[];\n}\n\nexport interface PostinstallOrchestratorInput {\n readonly projectRoot: string;\n readonly manifestType: 'project' | 'plugin';\n readonly candidates: readonly PostinstallCandidate[];\n /**\n * Optional explicit module-name list (from `postinstall.modules` in the\n * project manifest). When provided, overrides the names discovered from\n * `.uproject`'s `Modules[]`. An empty array = override to \"no modules\".\n */\n readonly moduleNameOverride?: readonly string[];\n}\n\n/**\n * Top-level postinstall flow:\n *\n * 1. Fast path: if the host is a plugin manifest, nothing runs.\n * 2. For each candidate plugin:\n * a. PolicyGate decides allow/deny. Deny → info diagnostic, skip.\n * b. Loader locates + transpiles + imports + validates the script.\n * c. Accumulate `(plugin, definition)` pairs into a plan.\n * 3. Build a single {@link UnrealSourceCatalog} covering every module\n * any script wants to touch.\n * 4. Apply injectors idempotently per zone.\n *\n * Always returns a {@link Result}; never throws. The caller inspects\n * `diagnostics` to render errors/warnings/info lines.\n */\nexport class PostinstallOrchestrator {\n public constructor(\n private readonly loader: PostinstallLoader = new PostinstallLoader(),\n private readonly policy: PostinstallPolicyGate = new PostinstallPolicyGate(),\n private readonly catalogBuilder: UnrealSourceCatalogBuilder = new UnrealSourceCatalogBuilder(),\n private readonly buildInjector: BuildCsInjector = new BuildCsInjector(),\n private readonly targetInjector: TargetCsInjector = new TargetCsInjector(),\n private readonly projectInjector: UProjectInjector = new UProjectInjector(),\n private readonly projectFileLocator: ProjectFileLocator = new ProjectFileLocator(),\n private readonly uprojectReader: UProjectMetadataReader = new UProjectMetadataReader(),\n ) {}\n\n public async run(input: PostinstallOrchestratorInput): Promise<Result<PostinstallReport>> {\n const bag = new DiagnosticBag();\n const executed: string[] = [];\n const skipped: { packageName: string; reason: 'no-script' | 'policy-denied' }[] = [];\n\n if (input.manifestType !== 'project' || input.candidates.length === 0) {\n return bag.toResult({ executed, skipped });\n }\n\n const plan = await this.buildPlan(input.candidates, bag, skipped);\n if (plan.length === 0) {\n return bag.toResult({ executed, skipped });\n }\n\n const catalogResult = this.buildCatalog(input.projectRoot, plan, input.moduleNameOverride);\n if (!catalogResult.ok) {\n bag.mergeArray(catalogResult.diagnostics);\n return bag.toFailure();\n }\n const { catalog, moduleNames } = catalogResult.value;\n\n for (const entry of plan) {\n const applied = this.applyScript(input.projectRoot, entry, catalog, moduleNames, bag);\n if (applied) executed.push(entry.loaded.packageName);\n }\n\n return bag.toResult({ executed, skipped });\n }\n\n // ---- private helpers --------------------------------------------------\n\n private async buildPlan(\n candidates: readonly PostinstallCandidate[],\n bag: DiagnosticBag,\n skipped: { packageName: string; reason: 'no-script' | 'policy-denied' }[],\n ): Promise<Array<{ candidate: PostinstallCandidate; loaded: LoadedPostinstall }>> {\n const plan: Array<{ candidate: PostinstallCandidate; loaded: LoadedPostinstall }> = [];\n for (const candidate of candidates) {\n const decision = this.policy.evaluate(candidate.packageName, candidate.registry);\n if (!decision.allowed) {\n if (decision.denialDiagnostic) bag.add(decision.denialDiagnostic);\n skipped.push({ packageName: candidate.packageName, reason: 'policy-denied' });\n continue;\n }\n const loaded = await this.loader.load(candidate.packageName, candidate.pluginRoot);\n bag.mergeArray(loaded.diagnostics);\n if (!loaded.ok) continue; // Errors already in bag.\n if (loaded.value === null) {\n skipped.push({ packageName: candidate.packageName, reason: 'no-script' });\n continue;\n }\n plan.push({ candidate, loaded: loaded.value });\n }\n return plan;\n }\n\n private buildCatalog(\n projectRoot: string,\n plan: Array<{ candidate: PostinstallCandidate; loaded: LoadedPostinstall }>,\n moduleNameOverride: readonly string[] | undefined,\n ): Result<{ catalog: ReturnType<UnrealSourceCatalogBuilder['build']>; moduleNames: readonly string[] }> {\n const hasModuleSetup = plan.some((entry) => Boolean(entry.loaded.definition.setupModules));\n let moduleNames: readonly string[] = [];\n if (hasModuleSetup) {\n if (moduleNameOverride !== undefined) {\n // Explicit override from project manifest wins; empty = no modules.\n moduleNames = moduleNameOverride;\n } else {\n try {\n const uprojectPath = this.projectFileLocator.findProjectFile(projectRoot);\n moduleNames = this.uprojectReader.readModuleNames(uprojectPath);\n } catch (error) {\n return {\n ok: false,\n diagnostics: [createIoErrorDiagnostic(projectRoot, error instanceof Error ? error.message : String(error))],\n };\n }\n }\n }\n\n try {\n const catalog = this.catalogBuilder.build(projectRoot, moduleNames);\n return { ok: true, value: { catalog, moduleNames }, diagnostics: [] };\n } catch (error) {\n return {\n ok: false,\n diagnostics: [createUnknownErrorDiagnostic(error instanceof Error ? error.message : String(error))],\n };\n }\n }\n\n private applyScript(\n projectRoot: string,\n entry: { candidate: PostinstallCandidate; loaded: LoadedPostinstall },\n catalog: ReturnType<UnrealSourceCatalogBuilder['build']>,\n moduleNames: readonly string[],\n bag: DiagnosticBag,\n ): boolean {\n const { loaded } = entry;\n const def: PostinstallDefinition = loaded.definition;\n let anyFailed = false;\n\n if (def.setupModules) {\n for (const moduleName of moduleNames) {\n const moduleFile = catalog.moduleFiles.get(moduleName);\n if (!moduleFile) {\n bag.add(\n createUnknownErrorDiagnostic(`Missing module mapping for ${moduleName} (plugin ${loaded.packageName})`),\n );\n anyFailed = true;\n continue;\n }\n const result = this.buildInjector.apply(moduleFile, loaded.packageName, def.setupModules);\n this.absorb(result, bag, () => {\n anyFailed = true;\n });\n }\n }\n\n if (def.setupTargets) {\n for (const targetFile of catalog.targetFiles) {\n const result = this.targetInjector.apply(targetFile, loaded.packageName, def.setupTargets);\n this.absorb(result, bag, () => {\n anyFailed = true;\n });\n }\n }\n\n const projectPlugins = def.setupProject?.plugins ?? [];\n if (projectPlugins.length > 0) {\n try {\n const uprojectPath = this.projectFileLocator.findProjectFile(projectRoot);\n const result = this.projectInjector.apply(uprojectPath, projectPlugins);\n this.absorb(result, bag, () => {\n anyFailed = true;\n });\n } catch (error) {\n bag.add(createIoErrorDiagnostic(projectRoot, error instanceof Error ? error.message : String(error)));\n anyFailed = true;\n }\n }\n\n return !anyFailed;\n }\n\n private absorb(result: Result<void>, bag: DiagnosticBag, onFail: () => void): void {\n for (const d of result.diagnostics) bag.add(d as Diagnostic);\n if (!result.ok) onFail();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PostinstallOrchestrator.js","names":[],"sources":["../../../src/postinstall/runner/PostinstallOrchestrator.ts"],"sourcesContent":["import type { Diagnostic, Result } from '@uapkg/diagnostics';\nimport { createIoErrorDiagnostic, createUnknownErrorDiagnostic, DiagnosticBag } from '@uapkg/diagnostics';\nimport type { PostinstallDefinition } from '#cli/postinstall/api/PostinstallDsl.ts';\nimport { type LoadedPostinstall, PostinstallLoader } from '#cli/postinstall/loader/PostinstallLoader.ts';\nimport { PostinstallPolicyGate } from '#cli/postinstall/policy/PostinstallPolicyGate.ts';\nimport { BuildCsInjector } from '#cli/postinstall/unreal/BuildCsInjector.ts';\nimport { ProjectFileLocator } from '#cli/postinstall/unreal/ProjectFileLocator.ts';\nimport { TargetCsInjector } from '#cli/postinstall/unreal/TargetCsInjector.ts';\nimport { UnrealSourceCatalogBuilder } from '#cli/postinstall/unreal/UnrealSourceCatalog.ts';\nimport { UProjectInjector } from '#cli/postinstall/unreal/UProjectInjector.ts';\nimport { UProjectMetadataReader } from '#cli/postinstall/unreal/UProjectMetadataReader.ts';\n\n/**\n * Projection of an installer action that the orchestrator needs. The CLI\n * (Phase 8) will map `InstallAction` → `PostinstallCandidate` by filtering\n * `type === 'add' | 'update'` and joining with the lockfile for registry\n * metadata.\n */\nexport interface PostinstallCandidate {\n readonly packageName: string;\n readonly registry: string;\n /** Absolute path to the installed plugin root. */\n readonly pluginRoot: string;\n}\n\nexport interface PostinstallReport {\n readonly executed: readonly string[];\n readonly skipped: readonly { packageName: string; reason: 'no-script' | 'policy-denied' }[];\n}\n\nexport interface PostinstallOrchestratorInput {\n readonly projectRoot: string;\n readonly manifestType: 'project' | 'plugin';\n readonly candidates: readonly PostinstallCandidate[];\n /**\n * Optional explicit module-name list (from `postinstall.modules` in the\n * project manifest). When provided, overrides the names discovered from\n * `.uproject`'s `Modules[]`. An empty array = override to \"no modules\".\n */\n readonly moduleNameOverride?: readonly string[];\n}\n\n/**\n * Top-level postinstall flow:\n *\n * 1. Fast path: if the host is a plugin manifest, nothing runs.\n * 2. For each candidate plugin:\n * a. PolicyGate decides allow/deny. Deny → info diagnostic, skip.\n * b. Loader locates + transpiles + imports + validates the script.\n * c. Accumulate `(plugin, definition)` pairs into a plan.\n * 3. Build a single {@link UnrealSourceCatalog} covering every module\n * any script wants to touch.\n * 4. Apply injectors idempotently per zone.\n *\n * Always returns a {@link Result}; never throws. The caller inspects\n * `diagnostics` to render errors/warnings/info lines.\n */\nexport class PostinstallOrchestrator {\n public constructor(\n private readonly loader: PostinstallLoader = new PostinstallLoader(),\n private readonly policy: PostinstallPolicyGate = new PostinstallPolicyGate(),\n private readonly catalogBuilder: UnrealSourceCatalogBuilder = new UnrealSourceCatalogBuilder(),\n private readonly buildInjector: BuildCsInjector = new BuildCsInjector(),\n private readonly targetInjector: TargetCsInjector = new TargetCsInjector(),\n private readonly projectInjector: UProjectInjector = new UProjectInjector(),\n private readonly projectFileLocator: ProjectFileLocator = new ProjectFileLocator(),\n private readonly uprojectReader: UProjectMetadataReader = new UProjectMetadataReader(),\n ) {}\n\n public async run(input: PostinstallOrchestratorInput): Promise<Result<PostinstallReport>> {\n const bag = new DiagnosticBag();\n const executed: string[] = [];\n const skipped: { packageName: string; reason: 'no-script' | 'policy-denied' }[] = [];\n\n if (input.manifestType !== 'project' || input.candidates.length === 0) {\n return bag.toResult({ executed, skipped });\n }\n\n const plan = await this.buildPlan(input.candidates, bag, skipped);\n if (plan.length === 0) {\n return bag.toResult({ executed, skipped });\n }\n\n const catalogResult = this.buildCatalog(input.projectRoot, plan, input.moduleNameOverride);\n if (!catalogResult.ok) {\n bag.mergeArray(catalogResult.diagnostics);\n return bag.toFailure();\n }\n const { catalog, moduleNames } = catalogResult.value;\n\n for (const entry of plan) {\n const applied = this.applyScript(input.projectRoot, entry, catalog, moduleNames, bag);\n if (applied) executed.push(entry.loaded.packageName);\n }\n\n return bag.toResult({ executed, skipped });\n }\n\n // ---- private helpers --------------------------------------------------\n\n private async buildPlan(\n candidates: readonly PostinstallCandidate[],\n bag: DiagnosticBag,\n skipped: { packageName: string; reason: 'no-script' | 'policy-denied' }[],\n ): Promise<Array<{ candidate: PostinstallCandidate; loaded: LoadedPostinstall }>> {\n const plan: Array<{ candidate: PostinstallCandidate; loaded: LoadedPostinstall }> = [];\n for (const candidate of candidates) {\n const decision = this.policy.evaluate(candidate.packageName, candidate.registry);\n if (!decision.allowed) {\n if (decision.denialDiagnostic) bag.add(decision.denialDiagnostic);\n skipped.push({ packageName: candidate.packageName, reason: 'policy-denied' });\n continue;\n }\n const loaded = await this.loader.load(candidate.packageName, candidate.pluginRoot);\n bag.mergeArray(loaded.diagnostics);\n if (!loaded.ok) continue; // Errors already in bag.\n if (loaded.value === null) {\n skipped.push({ packageName: candidate.packageName, reason: 'no-script' });\n continue;\n }\n plan.push({ candidate, loaded: loaded.value });\n }\n return plan;\n }\n\n private buildCatalog(\n projectRoot: string,\n plan: Array<{ candidate: PostinstallCandidate; loaded: LoadedPostinstall }>,\n moduleNameOverride: readonly string[] | undefined,\n ): Result<{ catalog: ReturnType<UnrealSourceCatalogBuilder['build']>; moduleNames: readonly string[] }> {\n const hasModuleSetup = plan.some((entry) => Boolean(entry.loaded.definition.setupModules));\n let moduleNames: readonly string[] = [];\n if (hasModuleSetup) {\n if (moduleNameOverride !== undefined) {\n // Explicit override from project manifest wins; empty = no modules.\n moduleNames = moduleNameOverride;\n } else {\n try {\n const uprojectPath = this.projectFileLocator.findProjectFile(projectRoot);\n moduleNames = this.uprojectReader.readModuleNames(uprojectPath);\n } catch (error) {\n return {\n ok: false,\n diagnostics: [createIoErrorDiagnostic(projectRoot, error instanceof Error ? error.message : String(error))],\n };\n }\n }\n }\n\n try {\n const catalog = this.catalogBuilder.build(projectRoot, moduleNames);\n return { ok: true, value: { catalog, moduleNames }, diagnostics: [] };\n } catch (error) {\n return {\n ok: false,\n diagnostics: [createUnknownErrorDiagnostic(error instanceof Error ? error.message : String(error))],\n };\n }\n }\n\n private applyScript(\n projectRoot: string,\n entry: { candidate: PostinstallCandidate; loaded: LoadedPostinstall },\n catalog: ReturnType<UnrealSourceCatalogBuilder['build']>,\n moduleNames: readonly string[],\n bag: DiagnosticBag,\n ): boolean {\n const { loaded } = entry;\n const def: PostinstallDefinition = loaded.definition;\n let anyFailed = false;\n\n if (def.setupModules) {\n for (const moduleName of moduleNames) {\n const moduleFile = catalog.moduleFiles.get(moduleName);\n if (!moduleFile) {\n bag.add(\n createUnknownErrorDiagnostic(`Missing module mapping for ${moduleName} (plugin ${loaded.packageName})`),\n );\n anyFailed = true;\n continue;\n }\n const result = this.buildInjector.apply(moduleFile, loaded.packageName, def.setupModules);\n this.absorb(result, bag, () => {\n anyFailed = true;\n });\n }\n }\n\n if (def.setupTargets) {\n for (const targetFile of catalog.targetFiles) {\n const result = this.targetInjector.apply(targetFile, loaded.packageName, def.setupTargets);\n this.absorb(result, bag, () => {\n anyFailed = true;\n });\n }\n }\n\n const projectPlugins = def.setupProject?.plugins ?? [];\n if (projectPlugins.length > 0) {\n try {\n const uprojectPath = this.projectFileLocator.findProjectFile(projectRoot);\n const result = this.projectInjector.apply(uprojectPath, projectPlugins);\n this.absorb(result, bag, () => {\n anyFailed = true;\n });\n } catch (error) {\n bag.add(createIoErrorDiagnostic(projectRoot, error instanceof Error ? error.message : String(error)));\n anyFailed = true;\n }\n }\n\n return !anyFailed;\n }\n\n private absorb(result: Result<void>, bag: DiagnosticBag, onFail: () => void): void {\n for (const d of result.diagnostics) bag.add(d as Diagnostic);\n if (!result.ok) onFail();\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BuildCsInjector.js","names":[],"sources":["../../../src/postinstall/unreal/BuildCsInjector.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { createIoErrorDiagnostic, fail, ok, type Result } from '@uapkg/diagnostics';\nimport type { ZoneDefinition } from '../api/PostinstallDsl.js';\nimport { MarkerBlockEditor } from '../markers/MarkerBlockEditor.js';\nimport { CSharpInjectionEngine } from './CSharpInjectionEngine.js';\nimport { CSharpStructureAnalyzer } from './CSharpStructureAnalyzer.js';\nimport { CSharpWrapperFactory } from './CSharpWrapperFactory.js';\nimport { getWrapperClassName } from './PluginHash.js';\n\nconst BUILD_CS_ZONES = ['module-includes', 'module-class-body', 'module-constructor'] as const;\n\n/**\n * Injects a plugin's `setupModules` content into a single `*.Build.cs` file.\n *\n * The operation is idempotent: re-running overwrites the owned marker blocks.\n * All failures are returned as `Result.fail` — this class never throws. Before\n * mutation, pre-existing owned marker blocks are checked for corruption\n * (orphaned / nested markers) and surfaced as `POSTINSTALL_MARKER_CORRUPT`.\n */\nexport class BuildCsInjector {\n public constructor(\n private readonly analyzer: CSharpStructureAnalyzer = new CSharpStructureAnalyzer(),\n private readonly wrapperFactory: CSharpWrapperFactory = new CSharpWrapperFactory(),\n private readonly injector: CSharpInjectionEngine = new CSharpInjectionEngine(),\n private readonly markerEditor: MarkerBlockEditor = new MarkerBlockEditor(),\n ) {}\n\n public apply(filePath: string, pluginName: string, zones: ZoneDefinition): Result<void> {\n try {\n let source = fs.readFileSync(filePath, 'utf-8');\n\n for (const zone of BUILD_CS_ZONES) {\n const check = this.markerEditor.validate(source, pluginName, zone, filePath);\n if (!check.ok) return fail(check.diagnostics);\n }\n\n if (zones.includes?.trim()) {\n source = this.injector.applyIncludes(source, pluginName, zones.includes, 'module-includes');\n }\n if (zones.classBody?.trim()) {\n const parsedForWrapper = this.analyzer.parseModule(filePath, source);\n const wrapperClassName = getWrapperClassName(pluginName);\n const wrapper = this.wrapperFactory.createWrapper(wrapperClassName, 'ModuleRules', 'rules', zones.classBody);\n source = this.injector.applyClassWrapper(parsedForWrapper, pluginName, wrapper, 'module-class-body');\n\n const parsedForCtor = this.analyzer.parseModule(filePath, source);\n source = this.injector.applyConstructorCall(\n parsedForCtor,\n pluginName,\n `${wrapperClassName}.Apply(this)`,\n 'module-constructor',\n );\n }\n fs.writeFileSync(filePath, source, 'utf-8');\n return ok(undefined);\n } catch (error) {\n return fail([createIoErrorDiagnostic(filePath, error instanceof Error ? error.message : String(error))]);\n }\n }\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,iBAAiB;CAAC;CAAmB;CAAqB;AAAoB;;;;;;;;;AAUpF,IAAa,kBAAb,MAA6B;CAER;CACA;CACA;CACA;CAJnB,AAAO,YACL,AAAiB,WAAoC,IAAI,wBAAwB,GACjF,AAAiB,iBAAuC,IAAI,qBAAqB,GACjF,AAAiB,WAAkC,IAAI,sBAAsB,GAC7E,AAAiB,eAAkC,IAAI,kBAAkB,GACzE;EAJiB;EACA;EACA;EACA;CAChB;CAEH,AAAO,MAAM,UAAkB,YAAoB,OAAqC;EACtF,IAAI;GACF,IAAI,SAAS,GAAG,aAAa,UAAU,OAAO;GAE9C,KAAK,MAAM,QAAQ,gBAAgB;IACjC,MAAM,QAAQ,KAAK,aAAa,SAAS,QAAQ,YAAY,MAAM,QAAQ;IAC3E,IAAI,CAAC,MAAM,IAAI,OAAO,KAAK,MAAM,WAAW;GAC9C;GAEA,IAAI,MAAM,UAAU,KAAK,GACvB,SAAS,KAAK,SAAS,cAAc,QAAQ,YAAY,MAAM,UAAU,iBAAiB;GAE5F,IAAI,MAAM,WAAW,KAAK,GAAG;IAC3B,MAAM,mBAAmB,KAAK,SAAS,YAAY,UAAU,MAAM;IACnE,MAAM,mBAAmB,oBAAoB,UAAU;IACvD,MAAM,UAAU,KAAK,eAAe,cAAc,kBAAkB,eAAe,SAAS,MAAM,SAAS;IAC3G,SAAS,KAAK,SAAS,kBAAkB,kBAAkB,YAAY,SAAS,mBAAmB;IAEnG,MAAM,gBAAgB,KAAK,SAAS,YAAY,UAAU,MAAM;IAChE,SAAS,KAAK,SAAS,qBACrB,eACA,YACA,GAAG,iBAAiB,eACpB,oBACF;GACF;GACA,GAAG,cAAc,UAAU,QAAQ,OAAO;GAC1C,OAAO,GAAG,MAAS;EACrB,SAAS,OAAO;GACd,OAAO,KAAK,CAAC,wBAAwB,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,CAAC,CAAC;EACzG;CACF;AACF"}
1
+ {"version":3,"file":"BuildCsInjector.js","names":[],"sources":["../../../src/postinstall/unreal/BuildCsInjector.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { createIoErrorDiagnostic, fail, ok, type Result } from '@uapkg/diagnostics';\nimport type { ZoneDefinition } from '#cli/postinstall/api/PostinstallDsl.ts';\nimport { MarkerBlockEditor } from '#cli/postinstall/markers/MarkerBlockEditor.ts';\nimport { CSharpInjectionEngine } from '#cli/postinstall/unreal/CSharpInjectionEngine.ts';\nimport { CSharpStructureAnalyzer } from '#cli/postinstall/unreal/CSharpStructureAnalyzer.ts';\nimport { CSharpWrapperFactory } from '#cli/postinstall/unreal/CSharpWrapperFactory.ts';\nimport { getWrapperClassName } from '#cli/postinstall/unreal/PluginHash.ts';\n\nconst BUILD_CS_ZONES = ['module-includes', 'module-class-body', 'module-constructor'] as const;\n\n/**\n * Injects a plugin's `setupModules` content into a single `*.Build.cs` file.\n *\n * The operation is idempotent: re-running overwrites the owned marker blocks.\n * All failures are returned as `Result.fail` — this class never throws. Before\n * mutation, pre-existing owned marker blocks are checked for corruption\n * (orphaned / nested markers) and surfaced as `POSTINSTALL_MARKER_CORRUPT`.\n */\nexport class BuildCsInjector {\n public constructor(\n private readonly analyzer: CSharpStructureAnalyzer = new CSharpStructureAnalyzer(),\n private readonly wrapperFactory: CSharpWrapperFactory = new CSharpWrapperFactory(),\n private readonly injector: CSharpInjectionEngine = new CSharpInjectionEngine(),\n private readonly markerEditor: MarkerBlockEditor = new MarkerBlockEditor(),\n ) {}\n\n public apply(filePath: string, pluginName: string, zones: ZoneDefinition): Result<void> {\n try {\n let source = fs.readFileSync(filePath, 'utf-8');\n\n for (const zone of BUILD_CS_ZONES) {\n const check = this.markerEditor.validate(source, pluginName, zone, filePath);\n if (!check.ok) return fail(check.diagnostics);\n }\n\n if (zones.includes?.trim()) {\n source = this.injector.applyIncludes(source, pluginName, zones.includes, 'module-includes');\n }\n if (zones.classBody?.trim()) {\n const parsedForWrapper = this.analyzer.parseModule(filePath, source);\n const wrapperClassName = getWrapperClassName(pluginName);\n const wrapper = this.wrapperFactory.createWrapper(wrapperClassName, 'ModuleRules', 'rules', zones.classBody);\n source = this.injector.applyClassWrapper(parsedForWrapper, pluginName, wrapper, 'module-class-body');\n\n const parsedForCtor = this.analyzer.parseModule(filePath, source);\n source = this.injector.applyConstructorCall(\n parsedForCtor,\n pluginName,\n `${wrapperClassName}.Apply(this)`,\n 'module-constructor',\n );\n }\n fs.writeFileSync(filePath, source, 'utf-8');\n return ok(undefined);\n } catch (error) {\n return fail([createIoErrorDiagnostic(filePath, error instanceof Error ? error.message : String(error))]);\n }\n }\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,iBAAiB;CAAC;CAAmB;CAAqB;AAAoB;;;;;;;;;AAUpF,IAAa,kBAAb,MAA6B;CAER;CACA;CACA;CACA;CAJnB,AAAO,YACL,AAAiB,WAAoC,IAAI,wBAAwB,GACjF,AAAiB,iBAAuC,IAAI,qBAAqB,GACjF,AAAiB,WAAkC,IAAI,sBAAsB,GAC7E,AAAiB,eAAkC,IAAI,kBAAkB,GACzE;EAJiB;EACA;EACA;EACA;CAChB;CAEH,AAAO,MAAM,UAAkB,YAAoB,OAAqC;EACtF,IAAI;GACF,IAAI,SAAS,GAAG,aAAa,UAAU,OAAO;GAE9C,KAAK,MAAM,QAAQ,gBAAgB;IACjC,MAAM,QAAQ,KAAK,aAAa,SAAS,QAAQ,YAAY,MAAM,QAAQ;IAC3E,IAAI,CAAC,MAAM,IAAI,OAAO,KAAK,MAAM,WAAW;GAC9C;GAEA,IAAI,MAAM,UAAU,KAAK,GACvB,SAAS,KAAK,SAAS,cAAc,QAAQ,YAAY,MAAM,UAAU,iBAAiB;GAE5F,IAAI,MAAM,WAAW,KAAK,GAAG;IAC3B,MAAM,mBAAmB,KAAK,SAAS,YAAY,UAAU,MAAM;IACnE,MAAM,mBAAmB,oBAAoB,UAAU;IACvD,MAAM,UAAU,KAAK,eAAe,cAAc,kBAAkB,eAAe,SAAS,MAAM,SAAS;IAC3G,SAAS,KAAK,SAAS,kBAAkB,kBAAkB,YAAY,SAAS,mBAAmB;IAEnG,MAAM,gBAAgB,KAAK,SAAS,YAAY,UAAU,MAAM;IAChE,SAAS,KAAK,SAAS,qBACrB,eACA,YACA,GAAG,iBAAiB,eACpB,oBACF;GACF;GACA,GAAG,cAAc,UAAU,QAAQ,OAAO;GAC1C,OAAO,GAAG,MAAS;EACrB,SAAS,OAAO;GACd,OAAO,KAAK,CAAC,wBAAwB,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,CAAC,CAAC;EACzG;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"CSharpInjectionEngine.js","names":[],"sources":["../../../src/postinstall/unreal/CSharpInjectionEngine.ts"],"sourcesContent":["import { MarkerBlockService } from '../markers/MarkerBlockService.js';\nimport type { ParsedCSharpFile } from './CSharpStructures.js';\n\n/**\n * Applies marker-delimited edits to a parsed C# file at three well-known anchors:\n *\n * 1. `applyIncludes` — `using ...;` block just after the last top-level `using`.\n * 2. `applyClassWrapper` — nested helper class inside the rules class body.\n * 3. `applyConstructorCall` — a single statement at the top of the constructor.\n *\n * Every edit is wrapped in an owned `UAPKG-BEGIN/END` marker block so re-running\n * uapkg is idempotent. Existing owned blocks are stripped before re-insert.\n */\nexport class CSharpInjectionEngine {\n public constructor(private readonly markerBlock: MarkerBlockService = new MarkerBlockService()) {}\n\n public applyIncludes(source: string, pluginName: string, includesContent: string, zone: string): string {\n const withoutOwnedBlock = this.markerBlock.stripOwnedBlock(source, pluginName, zone);\n const includeBlock = this.markerBlock.createBlock(pluginName, zone, includesContent);\n const usingMatches = [...withoutOwnedBlock.matchAll(/^[ \\t]*using\\s+[^;]+;\\s*$/gm)];\n if (usingMatches.length === 0) {\n return `${includeBlock}\\n${withoutOwnedBlock}`.trimStart();\n }\n\n const lastUsing = usingMatches[usingMatches.length - 1];\n const lastUsingIndex = (lastUsing.index ?? 0) + lastUsing[0].length;\n return `${withoutOwnedBlock.slice(0, lastUsingIndex)}\\n${includeBlock}\\n${withoutOwnedBlock.slice(lastUsingIndex).trimStart()}`;\n }\n\n public applyClassWrapper(parsed: ParsedCSharpFile, pluginName: string, wrapperContent: string, zone: string): string {\n const classStart = parsed.classInfo.classOpenBraceIndex + 1;\n const classEnd = parsed.classInfo.classCloseBraceIndex;\n const classBody = parsed.source.slice(classStart, classEnd);\n const stripped = this.markerBlock.stripOwnedBlock(classBody, pluginName, zone).trimStart();\n const indent = this.detectClassMemberIndent(parsed.source, parsed.classInfo.classOpenBraceIndex);\n const block = this.markerBlock.createBlock(pluginName, zone, wrapperContent, indent);\n const nextBody = `\\n${block}\\n\\n${stripped}`.replace(/\\n{3,}/g, '\\n\\n');\n return `${parsed.source.slice(0, classStart)}${nextBody}${parsed.source.slice(classEnd)}`;\n }\n\n public applyConstructorCall(\n parsed: ParsedCSharpFile,\n pluginName: string,\n callExpression: string,\n zone: string,\n ): string {\n const bodyStart = parsed.constructorInfo.bodyOpenBraceIndex + 1;\n const bodyEnd = parsed.constructorInfo.bodyCloseBraceIndex;\n const body = parsed.source.slice(bodyStart, bodyEnd);\n const withoutOwnedBlock = this.markerBlock.stripOwnedBlock(body, pluginName, zone).trimStart();\n const indent = this.detectConstructorIndent(parsed.source, parsed.constructorInfo.bodyOpenBraceIndex);\n const block = this.markerBlock.createBlock(pluginName, zone, `${callExpression};`, indent);\n const nextBody = `\\n${block}\\n${withoutOwnedBlock.length > 0 ? `\\n${withoutOwnedBlock}` : ''}`.replace(\n /\\n{3,}/g,\n '\\n\\n',\n );\n return `${parsed.source.slice(0, bodyStart)}${nextBody}${parsed.source.slice(bodyEnd)}`;\n }\n\n private detectClassMemberIndent(source: string, classOpenBraceIndex: number): string {\n const lineStart = source.lastIndexOf('\\n', classOpenBraceIndex);\n const classLine = source.slice(lineStart + 1, classOpenBraceIndex);\n const classIndent = classLine.match(/^\\s*/)?.[0] ?? '';\n return `${classIndent} `;\n }\n\n private detectConstructorIndent(source: string, constructorOpenBraceIndex: number): string {\n const lineStart = source.lastIndexOf('\\n', constructorOpenBraceIndex);\n const constructorLine = source.slice(lineStart + 1, constructorOpenBraceIndex);\n const constructorIndent = constructorLine.match(/^\\s*/)?.[0] ?? '';\n return `${constructorIndent} `;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,IAAa,wBAAb,MAAmC;CACG;CAApC,AAAO,YAAY,AAAiB,cAAkC,IAAI,mBAAmB,GAAG;EAA5D;CAA6D;CAEjG,AAAO,cAAc,QAAgB,YAAoB,iBAAyB,MAAsB;EACtG,MAAM,oBAAoB,KAAK,YAAY,gBAAgB,QAAQ,YAAY,IAAI;EACnF,MAAM,eAAe,KAAK,YAAY,YAAY,YAAY,MAAM,eAAe;EACnF,MAAM,eAAe,CAAC,GAAG,kBAAkB,SAAS,6BAA6B,CAAC;EAClF,IAAI,aAAa,WAAW,GAC1B,OAAO,GAAG,aAAa,IAAI,oBAAoB,UAAU;EAG3D,MAAM,YAAY,aAAa,aAAa,SAAS;EACrD,MAAM,kBAAkB,UAAU,SAAS,KAAK,UAAU,EAAE,CAAC;EAC7D,OAAO,GAAG,kBAAkB,MAAM,GAAG,cAAc,EAAE,IAAI,aAAa,IAAI,kBAAkB,MAAM,cAAc,CAAC,CAAC,UAAU;CAC9H;CAEA,AAAO,kBAAkB,QAA0B,YAAoB,gBAAwB,MAAsB;EACnH,MAAM,aAAa,OAAO,UAAU,sBAAsB;EAC1D,MAAM,WAAW,OAAO,UAAU;EAClC,MAAM,YAAY,OAAO,OAAO,MAAM,YAAY,QAAQ;EAC1D,MAAM,WAAW,KAAK,YAAY,gBAAgB,WAAW,YAAY,IAAI,CAAC,CAAC,UAAU;EACzF,MAAM,SAAS,KAAK,wBAAwB,OAAO,QAAQ,OAAO,UAAU,mBAAmB;EAE/F,MAAM,WAAW,KADH,KAAK,YAAY,YAAY,YAAY,MAAM,gBAAgB,MACnD,EAAE,MAAM,WAAW,QAAQ,WAAW,MAAM;EACtE,OAAO,GAAG,OAAO,OAAO,MAAM,GAAG,UAAU,IAAI,WAAW,OAAO,OAAO,MAAM,QAAQ;CACxF;CAEA,AAAO,qBACL,QACA,YACA,gBACA,MACQ;EACR,MAAM,YAAY,OAAO,gBAAgB,qBAAqB;EAC9D,MAAM,UAAU,OAAO,gBAAgB;EACvC,MAAM,OAAO,OAAO,OAAO,MAAM,WAAW,OAAO;EACnD,MAAM,oBAAoB,KAAK,YAAY,gBAAgB,MAAM,YAAY,IAAI,CAAC,CAAC,UAAU;EAC7F,MAAM,SAAS,KAAK,wBAAwB,OAAO,QAAQ,OAAO,gBAAgB,kBAAkB;EAEpG,MAAM,WAAW,KADH,KAAK,YAAY,YAAY,YAAY,MAAM,GAAG,eAAe,IAAI,MACzD,EAAE,IAAI,kBAAkB,SAAS,IAAI,KAAK,sBAAsB,KAAK,QAC7F,WACA,MACF;EACA,OAAO,GAAG,OAAO,OAAO,MAAM,GAAG,SAAS,IAAI,WAAW,OAAO,OAAO,MAAM,OAAO;CACtF;CAEA,AAAQ,wBAAwB,QAAgB,qBAAqC;EACnF,MAAM,YAAY,OAAO,YAAY,MAAM,mBAAmB;EAG9D,OAAO,GAFW,OAAO,MAAM,YAAY,GAAG,mBAClB,CAAC,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,GAC9B;CACxB;CAEA,AAAQ,wBAAwB,QAAgB,2BAA2C;EACzF,MAAM,YAAY,OAAO,YAAY,MAAM,yBAAyB;EAGpE,OAAO,GAFiB,OAAO,MAAM,YAAY,GAAG,yBACZ,CAAC,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,GACpC;CAC9B;AACF"}
1
+ {"version":3,"file":"CSharpInjectionEngine.js","names":[],"sources":["../../../src/postinstall/unreal/CSharpInjectionEngine.ts"],"sourcesContent":["import { MarkerBlockService } from '#cli/postinstall/markers/MarkerBlockService.ts';\nimport type { ParsedCSharpFile } from '#cli/postinstall/unreal/CSharpStructures.ts';\n\n/**\n * Applies marker-delimited edits to a parsed C# file at three well-known anchors:\n *\n * 1. `applyIncludes` — `using ...;` block just after the last top-level `using`.\n * 2. `applyClassWrapper` — nested helper class inside the rules class body.\n * 3. `applyConstructorCall` — a single statement at the top of the constructor.\n *\n * Every edit is wrapped in an owned `UAPKG-BEGIN/END` marker block so re-running\n * uapkg is idempotent. Existing owned blocks are stripped before re-insert.\n */\nexport class CSharpInjectionEngine {\n public constructor(private readonly markerBlock: MarkerBlockService = new MarkerBlockService()) {}\n\n public applyIncludes(source: string, pluginName: string, includesContent: string, zone: string): string {\n const withoutOwnedBlock = this.markerBlock.stripOwnedBlock(source, pluginName, zone);\n const includeBlock = this.markerBlock.createBlock(pluginName, zone, includesContent);\n const usingMatches = [...withoutOwnedBlock.matchAll(/^[ \\t]*using\\s+[^;]+;\\s*$/gm)];\n if (usingMatches.length === 0) {\n return `${includeBlock}\\n${withoutOwnedBlock}`.trimStart();\n }\n\n const lastUsing = usingMatches[usingMatches.length - 1];\n const lastUsingIndex = (lastUsing.index ?? 0) + lastUsing[0].length;\n return `${withoutOwnedBlock.slice(0, lastUsingIndex)}\\n${includeBlock}\\n${withoutOwnedBlock.slice(lastUsingIndex).trimStart()}`;\n }\n\n public applyClassWrapper(parsed: ParsedCSharpFile, pluginName: string, wrapperContent: string, zone: string): string {\n const classStart = parsed.classInfo.classOpenBraceIndex + 1;\n const classEnd = parsed.classInfo.classCloseBraceIndex;\n const classBody = parsed.source.slice(classStart, classEnd);\n const stripped = this.markerBlock.stripOwnedBlock(classBody, pluginName, zone).trimStart();\n const indent = this.detectClassMemberIndent(parsed.source, parsed.classInfo.classOpenBraceIndex);\n const block = this.markerBlock.createBlock(pluginName, zone, wrapperContent, indent);\n const nextBody = `\\n${block}\\n\\n${stripped}`.replace(/\\n{3,}/g, '\\n\\n');\n return `${parsed.source.slice(0, classStart)}${nextBody}${parsed.source.slice(classEnd)}`;\n }\n\n public applyConstructorCall(\n parsed: ParsedCSharpFile,\n pluginName: string,\n callExpression: string,\n zone: string,\n ): string {\n const bodyStart = parsed.constructorInfo.bodyOpenBraceIndex + 1;\n const bodyEnd = parsed.constructorInfo.bodyCloseBraceIndex;\n const body = parsed.source.slice(bodyStart, bodyEnd);\n const withoutOwnedBlock = this.markerBlock.stripOwnedBlock(body, pluginName, zone).trimStart();\n const indent = this.detectConstructorIndent(parsed.source, parsed.constructorInfo.bodyOpenBraceIndex);\n const block = this.markerBlock.createBlock(pluginName, zone, `${callExpression};`, indent);\n const nextBody = `\\n${block}\\n${withoutOwnedBlock.length > 0 ? `\\n${withoutOwnedBlock}` : ''}`.replace(\n /\\n{3,}/g,\n '\\n\\n',\n );\n return `${parsed.source.slice(0, bodyStart)}${nextBody}${parsed.source.slice(bodyEnd)}`;\n }\n\n private detectClassMemberIndent(source: string, classOpenBraceIndex: number): string {\n const lineStart = source.lastIndexOf('\\n', classOpenBraceIndex);\n const classLine = source.slice(lineStart + 1, classOpenBraceIndex);\n const classIndent = classLine.match(/^\\s*/)?.[0] ?? '';\n return `${classIndent} `;\n }\n\n private detectConstructorIndent(source: string, constructorOpenBraceIndex: number): string {\n const lineStart = source.lastIndexOf('\\n', constructorOpenBraceIndex);\n const constructorLine = source.slice(lineStart + 1, constructorOpenBraceIndex);\n const constructorIndent = constructorLine.match(/^\\s*/)?.[0] ?? '';\n return `${constructorIndent} `;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,IAAa,wBAAb,MAAmC;CACG;CAApC,AAAO,YAAY,AAAiB,cAAkC,IAAI,mBAAmB,GAAG;EAA5D;CAA6D;CAEjG,AAAO,cAAc,QAAgB,YAAoB,iBAAyB,MAAsB;EACtG,MAAM,oBAAoB,KAAK,YAAY,gBAAgB,QAAQ,YAAY,IAAI;EACnF,MAAM,eAAe,KAAK,YAAY,YAAY,YAAY,MAAM,eAAe;EACnF,MAAM,eAAe,CAAC,GAAG,kBAAkB,SAAS,6BAA6B,CAAC;EAClF,IAAI,aAAa,WAAW,GAC1B,OAAO,GAAG,aAAa,IAAI,oBAAoB,UAAU;EAG3D,MAAM,YAAY,aAAa,aAAa,SAAS;EACrD,MAAM,kBAAkB,UAAU,SAAS,KAAK,UAAU,EAAE,CAAC;EAC7D,OAAO,GAAG,kBAAkB,MAAM,GAAG,cAAc,EAAE,IAAI,aAAa,IAAI,kBAAkB,MAAM,cAAc,CAAC,CAAC,UAAU;CAC9H;CAEA,AAAO,kBAAkB,QAA0B,YAAoB,gBAAwB,MAAsB;EACnH,MAAM,aAAa,OAAO,UAAU,sBAAsB;EAC1D,MAAM,WAAW,OAAO,UAAU;EAClC,MAAM,YAAY,OAAO,OAAO,MAAM,YAAY,QAAQ;EAC1D,MAAM,WAAW,KAAK,YAAY,gBAAgB,WAAW,YAAY,IAAI,CAAC,CAAC,UAAU;EACzF,MAAM,SAAS,KAAK,wBAAwB,OAAO,QAAQ,OAAO,UAAU,mBAAmB;EAE/F,MAAM,WAAW,KADH,KAAK,YAAY,YAAY,YAAY,MAAM,gBAAgB,MACnD,EAAE,MAAM,WAAW,QAAQ,WAAW,MAAM;EACtE,OAAO,GAAG,OAAO,OAAO,MAAM,GAAG,UAAU,IAAI,WAAW,OAAO,OAAO,MAAM,QAAQ;CACxF;CAEA,AAAO,qBACL,QACA,YACA,gBACA,MACQ;EACR,MAAM,YAAY,OAAO,gBAAgB,qBAAqB;EAC9D,MAAM,UAAU,OAAO,gBAAgB;EACvC,MAAM,OAAO,OAAO,OAAO,MAAM,WAAW,OAAO;EACnD,MAAM,oBAAoB,KAAK,YAAY,gBAAgB,MAAM,YAAY,IAAI,CAAC,CAAC,UAAU;EAC7F,MAAM,SAAS,KAAK,wBAAwB,OAAO,QAAQ,OAAO,gBAAgB,kBAAkB;EAEpG,MAAM,WAAW,KADH,KAAK,YAAY,YAAY,YAAY,MAAM,GAAG,eAAe,IAAI,MACzD,EAAE,IAAI,kBAAkB,SAAS,IAAI,KAAK,sBAAsB,KAAK,QAC7F,WACA,MACF;EACA,OAAO,GAAG,OAAO,OAAO,MAAM,GAAG,SAAS,IAAI,WAAW,OAAO,OAAO,MAAM,OAAO;CACtF;CAEA,AAAQ,wBAAwB,QAAgB,qBAAqC;EACnF,MAAM,YAAY,OAAO,YAAY,MAAM,mBAAmB;EAG9D,OAAO,GAFW,OAAO,MAAM,YAAY,GAAG,mBAClB,CAAC,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,GAC9B;CACxB;CAEA,AAAQ,wBAAwB,QAAgB,2BAA2C;EACzF,MAAM,YAAY,OAAO,YAAY,MAAM,yBAAyB;EAGpE,OAAO,GAFiB,OAAO,MAAM,YAAY,GAAG,yBACZ,CAAC,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,GACpC;CAC9B;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"CSharpStructureAnalyzer.d.ts","names":[],"sources":["../../../src/postinstall/unreal/CSharpStructureAnalyzer.ts"],"mappings":";;;;;;;;;;;;;;cAiBa;EACJ,YAAY,kBAAkB,iBAAiB;EAI/C,YAAY,kBAAkB,iBAAiB;UAQ9C;UAqBA;UAoBA;UAgCA"}
1
+ {"version":3,"file":"CSharpStructureAnalyzer.d.ts","names":[],"sources":["../../../src/postinstall/unreal/CSharpStructureAnalyzer.ts"],"mappings":";;;;;;;;;;;;;;cAsBa;EACJ,YAAY,kBAAkB,iBAAiB;EAI/C,YAAY,kBAAkB,iBAAiB;UAQ9C;UAqBA;UAoBA;UAgCA"}
@@ -1 +1 @@
1
- {"version":3,"file":"CSharpStructureAnalyzer.js","names":[],"sources":["../../../src/postinstall/unreal/CSharpStructureAnalyzer.ts"],"sourcesContent":["import type { CSharpFileKind, ParsedClass, ParsedConstructor, ParsedCSharpFile } from './CSharpStructures.js';\nimport { PrettyParseError } from './PrettyParseError.js';\n\nconst CLASS_REGEX = /\\bclass\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*:\\s*([^{\\r\\n]+)/g;\n\n/**\n * Lightweight C# structural parser tailored to Unreal build files.\n *\n * We do NOT invoke the Roslyn / csharp-ast graph for analysis because the\n * pipeline only needs three anchors:\n * 1. The single class inheriting `ModuleRules` / `TargetRules`.\n * 2. Its constructor matching `(ModuleRules|TargetRules)(Type Target) : base(Target)`.\n * 3. The body braces of (1) and (2) so injectors can splice content.\n *\n * Lighter-than-AST parsing keeps this file < 200 LOC and avoids pulling a\n * heavyweight runtime into `uapkg install`.\n */\nexport class CSharpStructureAnalyzer {\n public parseModule(filePath: string, source: string): ParsedCSharpFile {\n return this.parseFile('module', filePath, source, 'ModuleRules', 'ReadOnlyTargetRules');\n }\n\n public parseTarget(filePath: string, source: string): ParsedCSharpFile {\n const parsed = this.parseFile('target', filePath, source, 'TargetRules', 'TargetInfo');\n if (!/\\bType\\s*=\\s*TargetType\\.[A-Za-z_][A-Za-z0-9_]*\\s*;/.test(source)) {\n throw new PrettyParseError(filePath, 'TargetRules file missing `Type = TargetType.X;` assignment', source, 0);\n }\n return parsed;\n }\n\n private parseFile(\n kind: CSharpFileKind,\n filePath: string,\n source: string,\n expectedBaseClass: string,\n expectedConstructorArgType: string,\n ): ParsedCSharpFile {\n const matchingClasses = this.findClassesByBase(source, expectedBaseClass);\n if (matchingClasses.length !== 1) {\n throw new PrettyParseError(\n filePath,\n `Expected exactly one class extending ${expectedBaseClass}, found ${matchingClasses.length}`,\n source,\n matchingClasses[0]?.classKeywordIndex ?? 0,\n );\n }\n const classInfo = matchingClasses[0];\n const constructorInfo = this.findConstructor(filePath, source, classInfo, expectedConstructorArgType);\n return { kind, classInfo, constructorInfo, source };\n }\n\n private findClassesByBase(source: string, expectedBaseClass: string): ParsedClass[] {\n const classes: ParsedClass[] = [];\n for (const match of source.matchAll(CLASS_REGEX)) {\n const className = match[1];\n const inheritance = match[2] ?? '';\n const inheritsExpected = inheritance\n .split(',')\n .map((item) => item.trim())\n .some((item) => item === expectedBaseClass);\n if (!inheritsExpected || !className) continue;\n\n const classKeywordIndex = match.index ?? 0;\n const classOpenBraceIndex = source.indexOf('{', classKeywordIndex);\n if (classOpenBraceIndex < 0) continue;\n const classCloseBraceIndex = this.findMatchingBrace(source, classOpenBraceIndex);\n classes.push({ className, classKeywordIndex, classOpenBraceIndex, classCloseBraceIndex });\n }\n return classes;\n }\n\n private findConstructor(\n filePath: string,\n source: string,\n classInfo: ParsedClass,\n expectedConstructorArgType: string,\n ): ParsedConstructor {\n const classBody = source.slice(classInfo.classOpenBraceIndex + 1, classInfo.classCloseBraceIndex);\n const signatureRegex = new RegExp(\n `\\\\bpublic\\\\s+${classInfo.className}\\\\s*\\\\(\\\\s*${expectedConstructorArgType}\\\\s+Target\\\\s*\\\\)\\\\s*:\\\\s*base\\\\s*\\\\(\\\\s*Target\\\\s*\\\\)`,\n 'g',\n );\n\n const matches = [...classBody.matchAll(signatureRegex)];\n if (matches.length !== 1) {\n throw new PrettyParseError(\n filePath,\n `Expected exactly one constructor signature for ${classInfo.className}(${expectedConstructorArgType} Target) : base(Target), found ${matches.length}`,\n source,\n classInfo.classKeywordIndex,\n );\n }\n\n const signatureRelativeIndex = matches[0].index ?? 0;\n const signatureIndex = classInfo.classOpenBraceIndex + 1 + signatureRelativeIndex;\n const bodyOpenBraceIndex = source.indexOf('{', signatureIndex);\n if (bodyOpenBraceIndex < 0) {\n throw new PrettyParseError(filePath, 'Constructor body opening brace not found', source, signatureIndex);\n }\n const bodyCloseBraceIndex = this.findMatchingBrace(source, bodyOpenBraceIndex);\n return { signatureIndex, bodyOpenBraceIndex, bodyCloseBraceIndex };\n }\n\n private findMatchingBrace(source: string, openIndex: number): number {\n let depth = 0;\n let inSingleLineComment = false;\n let inMultiLineComment = false;\n let inSingleQuote = false;\n let inDoubleQuote = false;\n for (let i = openIndex; i < source.length; i += 1) {\n const current = source[i];\n const next = source[i + 1];\n const prev = source[i - 1];\n\n if (inSingleLineComment) {\n if (current === '\\n') inSingleLineComment = false;\n continue;\n }\n if (inMultiLineComment) {\n if (prev === '*' && current === '/') inMultiLineComment = false;\n continue;\n }\n if (inSingleQuote) {\n if (current === \"'\" && prev !== '\\\\') inSingleQuote = false;\n continue;\n }\n if (inDoubleQuote) {\n if (current === '\"' && prev !== '\\\\') inDoubleQuote = false;\n continue;\n }\n if (current === '/' && next === '/') {\n inSingleLineComment = true;\n i += 1;\n continue;\n }\n if (current === '/' && next === '*') {\n inMultiLineComment = true;\n i += 1;\n continue;\n }\n if (current === \"'\") {\n inSingleQuote = true;\n continue;\n }\n if (current === '\"') {\n inDoubleQuote = true;\n continue;\n }\n if (current === '{') depth += 1;\n else if (current === '}') {\n depth -= 1;\n if (depth === 0) return i;\n }\n }\n throw new Error('Unbalanced braces in C# file');\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CSharpStructureAnalyzer.js","names":[],"sources":["../../../src/postinstall/unreal/CSharpStructureAnalyzer.ts"],"sourcesContent":["import type {\n CSharpFileKind,\n ParsedClass,\n ParsedConstructor,\n ParsedCSharpFile,\n} from '#cli/postinstall/unreal/CSharpStructures.ts';\nimport { PrettyParseError } from '#cli/postinstall/unreal/PrettyParseError.ts';\n\nconst CLASS_REGEX = /\\bclass\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*:\\s*([^{\\r\\n]+)/g;\n\n/**\n * Lightweight C# structural parser tailored to Unreal build files.\n *\n * We do NOT invoke the Roslyn / csharp-ast graph for analysis because the\n * pipeline only needs three anchors:\n * 1. The single class inheriting `ModuleRules` / `TargetRules`.\n * 2. Its constructor matching `(ModuleRules|TargetRules)(Type Target) : base(Target)`.\n * 3. The body braces of (1) and (2) so injectors can splice content.\n *\n * Lighter-than-AST parsing keeps this file < 200 LOC and avoids pulling a\n * heavyweight runtime into `uapkg install`.\n */\nexport class CSharpStructureAnalyzer {\n public parseModule(filePath: string, source: string): ParsedCSharpFile {\n return this.parseFile('module', filePath, source, 'ModuleRules', 'ReadOnlyTargetRules');\n }\n\n public parseTarget(filePath: string, source: string): ParsedCSharpFile {\n const parsed = this.parseFile('target', filePath, source, 'TargetRules', 'TargetInfo');\n if (!/\\bType\\s*=\\s*TargetType\\.[A-Za-z_][A-Za-z0-9_]*\\s*;/.test(source)) {\n throw new PrettyParseError(filePath, 'TargetRules file missing `Type = TargetType.X;` assignment', source, 0);\n }\n return parsed;\n }\n\n private parseFile(\n kind: CSharpFileKind,\n filePath: string,\n source: string,\n expectedBaseClass: string,\n expectedConstructorArgType: string,\n ): ParsedCSharpFile {\n const matchingClasses = this.findClassesByBase(source, expectedBaseClass);\n if (matchingClasses.length !== 1) {\n throw new PrettyParseError(\n filePath,\n `Expected exactly one class extending ${expectedBaseClass}, found ${matchingClasses.length}`,\n source,\n matchingClasses[0]?.classKeywordIndex ?? 0,\n );\n }\n const classInfo = matchingClasses[0];\n const constructorInfo = this.findConstructor(filePath, source, classInfo, expectedConstructorArgType);\n return { kind, classInfo, constructorInfo, source };\n }\n\n private findClassesByBase(source: string, expectedBaseClass: string): ParsedClass[] {\n const classes: ParsedClass[] = [];\n for (const match of source.matchAll(CLASS_REGEX)) {\n const className = match[1];\n const inheritance = match[2] ?? '';\n const inheritsExpected = inheritance\n .split(',')\n .map((item) => item.trim())\n .some((item) => item === expectedBaseClass);\n if (!inheritsExpected || !className) continue;\n\n const classKeywordIndex = match.index ?? 0;\n const classOpenBraceIndex = source.indexOf('{', classKeywordIndex);\n if (classOpenBraceIndex < 0) continue;\n const classCloseBraceIndex = this.findMatchingBrace(source, classOpenBraceIndex);\n classes.push({ className, classKeywordIndex, classOpenBraceIndex, classCloseBraceIndex });\n }\n return classes;\n }\n\n private findConstructor(\n filePath: string,\n source: string,\n classInfo: ParsedClass,\n expectedConstructorArgType: string,\n ): ParsedConstructor {\n const classBody = source.slice(classInfo.classOpenBraceIndex + 1, classInfo.classCloseBraceIndex);\n const signatureRegex = new RegExp(\n `\\\\bpublic\\\\s+${classInfo.className}\\\\s*\\\\(\\\\s*${expectedConstructorArgType}\\\\s+Target\\\\s*\\\\)\\\\s*:\\\\s*base\\\\s*\\\\(\\\\s*Target\\\\s*\\\\)`,\n 'g',\n );\n\n const matches = [...classBody.matchAll(signatureRegex)];\n if (matches.length !== 1) {\n throw new PrettyParseError(\n filePath,\n `Expected exactly one constructor signature for ${classInfo.className}(${expectedConstructorArgType} Target) : base(Target), found ${matches.length}`,\n source,\n classInfo.classKeywordIndex,\n );\n }\n\n const signatureRelativeIndex = matches[0].index ?? 0;\n const signatureIndex = classInfo.classOpenBraceIndex + 1 + signatureRelativeIndex;\n const bodyOpenBraceIndex = source.indexOf('{', signatureIndex);\n if (bodyOpenBraceIndex < 0) {\n throw new PrettyParseError(filePath, 'Constructor body opening brace not found', source, signatureIndex);\n }\n const bodyCloseBraceIndex = this.findMatchingBrace(source, bodyOpenBraceIndex);\n return { signatureIndex, bodyOpenBraceIndex, bodyCloseBraceIndex };\n }\n\n private findMatchingBrace(source: string, openIndex: number): number {\n let depth = 0;\n let inSingleLineComment = false;\n let inMultiLineComment = false;\n let inSingleQuote = false;\n let inDoubleQuote = false;\n for (let i = openIndex; i < source.length; i += 1) {\n const current = source[i];\n const next = source[i + 1];\n const prev = source[i - 1];\n\n if (inSingleLineComment) {\n if (current === '\\n') inSingleLineComment = false;\n continue;\n }\n if (inMultiLineComment) {\n if (prev === '*' && current === '/') inMultiLineComment = false;\n continue;\n }\n if (inSingleQuote) {\n if (current === \"'\" && prev !== '\\\\') inSingleQuote = false;\n continue;\n }\n if (inDoubleQuote) {\n if (current === '\"' && prev !== '\\\\') inDoubleQuote = false;\n continue;\n }\n if (current === '/' && next === '/') {\n inSingleLineComment = true;\n i += 1;\n continue;\n }\n if (current === '/' && next === '*') {\n inMultiLineComment = true;\n i += 1;\n continue;\n }\n if (current === \"'\") {\n inSingleQuote = true;\n continue;\n }\n if (current === '\"') {\n inDoubleQuote = true;\n continue;\n }\n if (current === '{') depth += 1;\n else if (current === '}') {\n depth -= 1;\n if (depth === 0) return i;\n }\n }\n throw new Error('Unbalanced braces in C# file');\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"TargetCsInjector.js","names":[],"sources":["../../../src/postinstall/unreal/TargetCsInjector.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { createIoErrorDiagnostic, fail, ok, type Result } from '@uapkg/diagnostics';\nimport type { ZoneDefinition } from '../api/PostinstallDsl.js';\nimport { MarkerBlockEditor } from '../markers/MarkerBlockEditor.js';\nimport { CSharpInjectionEngine } from './CSharpInjectionEngine.js';\nimport { CSharpStructureAnalyzer } from './CSharpStructureAnalyzer.js';\nimport { CSharpWrapperFactory } from './CSharpWrapperFactory.js';\nimport { getWrapperClassName } from './PluginHash.js';\n\nconst TARGET_CS_ZONES = ['target-includes', 'target-class-body', 'target-constructor'] as const;\n\n/**\n * Injects a plugin's `setupTargets` content into a single `*.Target.cs` file.\n *\n * Mirrors {@link BuildCsInjector} but targets `TargetRules`/`TargetInfo` and a\n * different marker-zone name-space. Never throws — errors become diagnostics.\n * Pre-existing owned marker blocks are integrity-checked before mutation and\n * surfaced as `POSTINSTALL_MARKER_CORRUPT` on failure.\n */\nexport class TargetCsInjector {\n public constructor(\n private readonly analyzer: CSharpStructureAnalyzer = new CSharpStructureAnalyzer(),\n private readonly wrapperFactory: CSharpWrapperFactory = new CSharpWrapperFactory(),\n private readonly injector: CSharpInjectionEngine = new CSharpInjectionEngine(),\n private readonly markerEditor: MarkerBlockEditor = new MarkerBlockEditor(),\n ) {}\n\n public apply(filePath: string, pluginName: string, zones: ZoneDefinition): Result<void> {\n try {\n let source = fs.readFileSync(filePath, 'utf-8');\n\n for (const zone of TARGET_CS_ZONES) {\n const check = this.markerEditor.validate(source, pluginName, zone, filePath);\n if (!check.ok) return fail(check.diagnostics);\n }\n\n if (zones.includes?.trim()) {\n source = this.injector.applyIncludes(source, pluginName, zones.includes, 'target-includes');\n }\n if (zones.classBody?.trim()) {\n const parsedForWrapper = this.analyzer.parseTarget(filePath, source);\n const wrapperClassName = getWrapperClassName(pluginName);\n const wrapper = this.wrapperFactory.createWrapper(wrapperClassName, 'TargetRules', 'target', zones.classBody);\n source = this.injector.applyClassWrapper(parsedForWrapper, pluginName, wrapper, 'target-class-body');\n\n const parsedForCtor = this.analyzer.parseTarget(filePath, source);\n source = this.injector.applyConstructorCall(\n parsedForCtor,\n pluginName,\n `${wrapperClassName}.Apply(this)`,\n 'target-constructor',\n );\n }\n fs.writeFileSync(filePath, source, 'utf-8');\n return ok(undefined);\n } catch (error) {\n return fail([createIoErrorDiagnostic(filePath, error instanceof Error ? error.message : String(error))]);\n }\n }\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,kBAAkB;CAAC;CAAmB;CAAqB;AAAoB;;;;;;;;;AAUrF,IAAa,mBAAb,MAA8B;CAET;CACA;CACA;CACA;CAJnB,AAAO,YACL,AAAiB,WAAoC,IAAI,wBAAwB,GACjF,AAAiB,iBAAuC,IAAI,qBAAqB,GACjF,AAAiB,WAAkC,IAAI,sBAAsB,GAC7E,AAAiB,eAAkC,IAAI,kBAAkB,GACzE;EAJiB;EACA;EACA;EACA;CAChB;CAEH,AAAO,MAAM,UAAkB,YAAoB,OAAqC;EACtF,IAAI;GACF,IAAI,SAAS,GAAG,aAAa,UAAU,OAAO;GAE9C,KAAK,MAAM,QAAQ,iBAAiB;IAClC,MAAM,QAAQ,KAAK,aAAa,SAAS,QAAQ,YAAY,MAAM,QAAQ;IAC3E,IAAI,CAAC,MAAM,IAAI,OAAO,KAAK,MAAM,WAAW;GAC9C;GAEA,IAAI,MAAM,UAAU,KAAK,GACvB,SAAS,KAAK,SAAS,cAAc,QAAQ,YAAY,MAAM,UAAU,iBAAiB;GAE5F,IAAI,MAAM,WAAW,KAAK,GAAG;IAC3B,MAAM,mBAAmB,KAAK,SAAS,YAAY,UAAU,MAAM;IACnE,MAAM,mBAAmB,oBAAoB,UAAU;IACvD,MAAM,UAAU,KAAK,eAAe,cAAc,kBAAkB,eAAe,UAAU,MAAM,SAAS;IAC5G,SAAS,KAAK,SAAS,kBAAkB,kBAAkB,YAAY,SAAS,mBAAmB;IAEnG,MAAM,gBAAgB,KAAK,SAAS,YAAY,UAAU,MAAM;IAChE,SAAS,KAAK,SAAS,qBACrB,eACA,YACA,GAAG,iBAAiB,eACpB,oBACF;GACF;GACA,GAAG,cAAc,UAAU,QAAQ,OAAO;GAC1C,OAAO,GAAG,MAAS;EACrB,SAAS,OAAO;GACd,OAAO,KAAK,CAAC,wBAAwB,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,CAAC,CAAC;EACzG;CACF;AACF"}
1
+ {"version":3,"file":"TargetCsInjector.js","names":[],"sources":["../../../src/postinstall/unreal/TargetCsInjector.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { createIoErrorDiagnostic, fail, ok, type Result } from '@uapkg/diagnostics';\nimport type { ZoneDefinition } from '#cli/postinstall/api/PostinstallDsl.ts';\nimport { MarkerBlockEditor } from '#cli/postinstall/markers/MarkerBlockEditor.ts';\nimport { CSharpInjectionEngine } from '#cli/postinstall/unreal/CSharpInjectionEngine.ts';\nimport { CSharpStructureAnalyzer } from '#cli/postinstall/unreal/CSharpStructureAnalyzer.ts';\nimport { CSharpWrapperFactory } from '#cli/postinstall/unreal/CSharpWrapperFactory.ts';\nimport { getWrapperClassName } from '#cli/postinstall/unreal/PluginHash.ts';\n\nconst TARGET_CS_ZONES = ['target-includes', 'target-class-body', 'target-constructor'] as const;\n\n/**\n * Injects a plugin's `setupTargets` content into a single `*.Target.cs` file.\n *\n * Mirrors {@link BuildCsInjector} but targets `TargetRules`/`TargetInfo` and a\n * different marker-zone name-space. Never throws — errors become diagnostics.\n * Pre-existing owned marker blocks are integrity-checked before mutation and\n * surfaced as `POSTINSTALL_MARKER_CORRUPT` on failure.\n */\nexport class TargetCsInjector {\n public constructor(\n private readonly analyzer: CSharpStructureAnalyzer = new CSharpStructureAnalyzer(),\n private readonly wrapperFactory: CSharpWrapperFactory = new CSharpWrapperFactory(),\n private readonly injector: CSharpInjectionEngine = new CSharpInjectionEngine(),\n private readonly markerEditor: MarkerBlockEditor = new MarkerBlockEditor(),\n ) {}\n\n public apply(filePath: string, pluginName: string, zones: ZoneDefinition): Result<void> {\n try {\n let source = fs.readFileSync(filePath, 'utf-8');\n\n for (const zone of TARGET_CS_ZONES) {\n const check = this.markerEditor.validate(source, pluginName, zone, filePath);\n if (!check.ok) return fail(check.diagnostics);\n }\n\n if (zones.includes?.trim()) {\n source = this.injector.applyIncludes(source, pluginName, zones.includes, 'target-includes');\n }\n if (zones.classBody?.trim()) {\n const parsedForWrapper = this.analyzer.parseTarget(filePath, source);\n const wrapperClassName = getWrapperClassName(pluginName);\n const wrapper = this.wrapperFactory.createWrapper(wrapperClassName, 'TargetRules', 'target', zones.classBody);\n source = this.injector.applyClassWrapper(parsedForWrapper, pluginName, wrapper, 'target-class-body');\n\n const parsedForCtor = this.analyzer.parseTarget(filePath, source);\n source = this.injector.applyConstructorCall(\n parsedForCtor,\n pluginName,\n `${wrapperClassName}.Apply(this)`,\n 'target-constructor',\n );\n }\n fs.writeFileSync(filePath, source, 'utf-8');\n return ok(undefined);\n } catch (error) {\n return fail([createIoErrorDiagnostic(filePath, error instanceof Error ? error.message : String(error))]);\n }\n }\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,kBAAkB;CAAC;CAAmB;CAAqB;AAAoB;;;;;;;;;AAUrF,IAAa,mBAAb,MAA8B;CAET;CACA;CACA;CACA;CAJnB,AAAO,YACL,AAAiB,WAAoC,IAAI,wBAAwB,GACjF,AAAiB,iBAAuC,IAAI,qBAAqB,GACjF,AAAiB,WAAkC,IAAI,sBAAsB,GAC7E,AAAiB,eAAkC,IAAI,kBAAkB,GACzE;EAJiB;EACA;EACA;EACA;CAChB;CAEH,AAAO,MAAM,UAAkB,YAAoB,OAAqC;EACtF,IAAI;GACF,IAAI,SAAS,GAAG,aAAa,UAAU,OAAO;GAE9C,KAAK,MAAM,QAAQ,iBAAiB;IAClC,MAAM,QAAQ,KAAK,aAAa,SAAS,QAAQ,YAAY,MAAM,QAAQ;IAC3E,IAAI,CAAC,MAAM,IAAI,OAAO,KAAK,MAAM,WAAW;GAC9C;GAEA,IAAI,MAAM,UAAU,KAAK,GACvB,SAAS,KAAK,SAAS,cAAc,QAAQ,YAAY,MAAM,UAAU,iBAAiB;GAE5F,IAAI,MAAM,WAAW,KAAK,GAAG;IAC3B,MAAM,mBAAmB,KAAK,SAAS,YAAY,UAAU,MAAM;IACnE,MAAM,mBAAmB,oBAAoB,UAAU;IACvD,MAAM,UAAU,KAAK,eAAe,cAAc,kBAAkB,eAAe,UAAU,MAAM,SAAS;IAC5G,SAAS,KAAK,SAAS,kBAAkB,kBAAkB,YAAY,SAAS,mBAAmB;IAEnG,MAAM,gBAAgB,KAAK,SAAS,YAAY,UAAU,MAAM;IAChE,SAAS,KAAK,SAAS,qBACrB,eACA,YACA,GAAG,iBAAiB,eACpB,oBACF;GACF;GACA,GAAG,cAAc,UAAU,QAAQ,OAAO;GAC1C,OAAO,GAAG,MAAS;EACrB,SAAS,OAAO;GACd,OAAO,KAAK,CAAC,wBAAwB,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,CAAC,CAAC;EACzG;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"UnrealSourceCatalog.js","names":[],"sources":["../../../src/postinstall/unreal/UnrealSourceCatalog.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { CSharpStructureAnalyzer } from './CSharpStructureAnalyzer.js';\nimport { ProjectFileLocator } from './ProjectFileLocator.js';\n\nexport interface SourceCatalog {\n /** ModuleName → absolute path of its `*.Build.cs`. */\n readonly moduleFiles: ReadonlyMap<string, string>;\n /** Absolute paths to every `*.Target.cs` in the project source tree. */\n readonly targetFiles: readonly string[];\n}\n\n/**\n * Scans the project source tree and validates every discovered `Build.cs` /\n * `Target.cs` with {@link CSharpStructureAnalyzer}. This is a one-shot\n * pre-flight check: the orchestrator runs it once before any injector mutates\n * a file, so we fail fast on malformed sources.\n */\nexport class UnrealSourceCatalogBuilder {\n public constructor(\n private readonly fileLocator: ProjectFileLocator = new ProjectFileLocator(),\n private readonly analyzer: CSharpStructureAnalyzer = new CSharpStructureAnalyzer(),\n ) {}\n\n public build(projectRoot: string, expectedModules: readonly string[]): SourceCatalog {\n const moduleFiles = this.resolveModules(projectRoot, expectedModules);\n const targetFiles = this.resolveTargets(projectRoot);\n return { moduleFiles, targetFiles };\n }\n\n private resolveModules(projectRoot: string, expectedModules: readonly string[]): Map<string, string> {\n const files = this.fileLocator.findBuildFiles(projectRoot);\n const discovered = new Map<string, string>();\n for (const filePath of files) {\n const source = fs.readFileSync(filePath, 'utf-8');\n const parsed = this.analyzer.parseModule(filePath, source);\n if (discovered.has(parsed.classInfo.className)) {\n throw new Error(\n `Duplicate ModuleRules class '${parsed.classInfo.className}' in ${discovered.get(parsed.classInfo.className)} and ${filePath}`,\n );\n }\n discovered.set(parsed.classInfo.className, filePath);\n }\n\n const missing = expectedModules.filter((name) => !discovered.has(name));\n if (missing.length > 0) {\n throw new Error(`Missing module Build.cs files for: ${missing.join(', ')}`);\n }\n\n const moduleFiles = new Map<string, string>();\n for (const moduleName of expectedModules) {\n moduleFiles.set(moduleName, discovered.get(moduleName) as string);\n }\n return moduleFiles;\n }\n\n private resolveTargets(projectRoot: string): string[] {\n const files = this.fileLocator.findTargetFiles(projectRoot);\n for (const filePath of files) {\n const source = fs.readFileSync(filePath, 'utf-8');\n this.analyzer.parseTarget(filePath, source);\n }\n return files;\n }\n}\n"],"mappings":";;;;;;;;;;;AAiBA,IAAa,6BAAb,MAAwC;CAEnB;CACA;CAFnB,AAAO,YACL,AAAiB,cAAkC,IAAI,mBAAmB,GAC1E,AAAiB,WAAoC,IAAI,wBAAwB,GACjF;EAFiB;EACA;CAChB;CAEH,AAAO,MAAM,aAAqB,iBAAmD;EAGnF,OAAO;GAAE,aAFW,KAAK,eAAe,aAAa,eAElC;GAAG,aADF,KAAK,eAAe,WACR;EAAE;CACpC;CAEA,AAAQ,eAAe,aAAqB,iBAAyD;EACnG,MAAM,QAAQ,KAAK,YAAY,eAAe,WAAW;EACzD,MAAM,6BAAa,IAAI,IAAoB;EAC3C,KAAK,MAAM,YAAY,OAAO;GAC5B,MAAM,SAAS,GAAG,aAAa,UAAU,OAAO;GAChD,MAAM,SAAS,KAAK,SAAS,YAAY,UAAU,MAAM;GACzD,IAAI,WAAW,IAAI,OAAO,UAAU,SAAS,GAC3C,MAAM,IAAI,MACR,gCAAgC,OAAO,UAAU,UAAU,OAAO,WAAW,IAAI,OAAO,UAAU,SAAS,EAAE,OAAO,UACtH;GAEF,WAAW,IAAI,OAAO,UAAU,WAAW,QAAQ;EACrD;EAEA,MAAM,UAAU,gBAAgB,QAAQ,SAAS,CAAC,WAAW,IAAI,IAAI,CAAC;EACtE,IAAI,QAAQ,SAAS,GACnB,MAAM,IAAI,MAAM,sCAAsC,QAAQ,KAAK,IAAI,GAAG;EAG5E,MAAM,8BAAc,IAAI,IAAoB;EAC5C,KAAK,MAAM,cAAc,iBACvB,YAAY,IAAI,YAAY,WAAW,IAAI,UAAU,CAAW;EAElE,OAAO;CACT;CAEA,AAAQ,eAAe,aAA+B;EACpD,MAAM,QAAQ,KAAK,YAAY,gBAAgB,WAAW;EAC1D,KAAK,MAAM,YAAY,OAAO;GAC5B,MAAM,SAAS,GAAG,aAAa,UAAU,OAAO;GAChD,KAAK,SAAS,YAAY,UAAU,MAAM;EAC5C;EACA,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"UnrealSourceCatalog.js","names":[],"sources":["../../../src/postinstall/unreal/UnrealSourceCatalog.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { CSharpStructureAnalyzer } from '#cli/postinstall/unreal/CSharpStructureAnalyzer.ts';\nimport { ProjectFileLocator } from '#cli/postinstall/unreal/ProjectFileLocator.ts';\n\nexport interface SourceCatalog {\n /** ModuleName → absolute path of its `*.Build.cs`. */\n readonly moduleFiles: ReadonlyMap<string, string>;\n /** Absolute paths to every `*.Target.cs` in the project source tree. */\n readonly targetFiles: readonly string[];\n}\n\n/**\n * Scans the project source tree and validates every discovered `Build.cs` /\n * `Target.cs` with {@link CSharpStructureAnalyzer}. This is a one-shot\n * pre-flight check: the orchestrator runs it once before any injector mutates\n * a file, so we fail fast on malformed sources.\n */\nexport class UnrealSourceCatalogBuilder {\n public constructor(\n private readonly fileLocator: ProjectFileLocator = new ProjectFileLocator(),\n private readonly analyzer: CSharpStructureAnalyzer = new CSharpStructureAnalyzer(),\n ) {}\n\n public build(projectRoot: string, expectedModules: readonly string[]): SourceCatalog {\n const moduleFiles = this.resolveModules(projectRoot, expectedModules);\n const targetFiles = this.resolveTargets(projectRoot);\n return { moduleFiles, targetFiles };\n }\n\n private resolveModules(projectRoot: string, expectedModules: readonly string[]): Map<string, string> {\n const files = this.fileLocator.findBuildFiles(projectRoot);\n const discovered = new Map<string, string>();\n for (const filePath of files) {\n const source = fs.readFileSync(filePath, 'utf-8');\n const parsed = this.analyzer.parseModule(filePath, source);\n if (discovered.has(parsed.classInfo.className)) {\n throw new Error(\n `Duplicate ModuleRules class '${parsed.classInfo.className}' in ${discovered.get(parsed.classInfo.className)} and ${filePath}`,\n );\n }\n discovered.set(parsed.classInfo.className, filePath);\n }\n\n const missing = expectedModules.filter((name) => !discovered.has(name));\n if (missing.length > 0) {\n throw new Error(`Missing module Build.cs files for: ${missing.join(', ')}`);\n }\n\n const moduleFiles = new Map<string, string>();\n for (const moduleName of expectedModules) {\n moduleFiles.set(moduleName, discovered.get(moduleName) as string);\n }\n return moduleFiles;\n }\n\n private resolveTargets(projectRoot: string): string[] {\n const files = this.fileLocator.findTargetFiles(projectRoot);\n for (const filePath of files) {\n const source = fs.readFileSync(filePath, 'utf-8');\n this.analyzer.parseTarget(filePath, source);\n }\n return files;\n }\n}\n"],"mappings":";;;;;;;;;;;AAiBA,IAAa,6BAAb,MAAwC;CAEnB;CACA;CAFnB,AAAO,YACL,AAAiB,cAAkC,IAAI,mBAAmB,GAC1E,AAAiB,WAAoC,IAAI,wBAAwB,GACjF;EAFiB;EACA;CAChB;CAEH,AAAO,MAAM,aAAqB,iBAAmD;EAGnF,OAAO;GAAE,aAFW,KAAK,eAAe,aAAa,eAElC;GAAG,aADF,KAAK,eAAe,WACR;EAAE;CACpC;CAEA,AAAQ,eAAe,aAAqB,iBAAyD;EACnG,MAAM,QAAQ,KAAK,YAAY,eAAe,WAAW;EACzD,MAAM,6BAAa,IAAI,IAAoB;EAC3C,KAAK,MAAM,YAAY,OAAO;GAC5B,MAAM,SAAS,GAAG,aAAa,UAAU,OAAO;GAChD,MAAM,SAAS,KAAK,SAAS,YAAY,UAAU,MAAM;GACzD,IAAI,WAAW,IAAI,OAAO,UAAU,SAAS,GAC3C,MAAM,IAAI,MACR,gCAAgC,OAAO,UAAU,UAAU,OAAO,WAAW,IAAI,OAAO,UAAU,SAAS,EAAE,OAAO,UACtH;GAEF,WAAW,IAAI,OAAO,UAAU,WAAW,QAAQ;EACrD;EAEA,MAAM,UAAU,gBAAgB,QAAQ,SAAS,CAAC,WAAW,IAAI,IAAI,CAAC;EACtE,IAAI,QAAQ,SAAS,GACnB,MAAM,IAAI,MAAM,sCAAsC,QAAQ,KAAK,IAAI,GAAG;EAG5E,MAAM,8BAAc,IAAI,IAAoB;EAC5C,KAAK,MAAM,cAAc,iBACvB,YAAY,IAAI,YAAY,WAAW,IAAI,UAAU,CAAW;EAElE,OAAO;CACT;CAEA,AAAQ,eAAe,aAA+B;EACpD,MAAM,QAAQ,KAAK,YAAY,gBAAgB,WAAW;EAC1D,KAAK,MAAM,YAAY,OAAO;GAC5B,MAAM,SAAS,GAAG,aAAa,UAAU,OAAO;GAChD,KAAK,SAAS,YAAY,UAAU,MAAM;EAC5C;EACA,OAAO;CACT;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"InkPromptService.js","names":[],"sources":["../../src/prompts/InkPromptService.tsx"],"sourcesContent":["import { Box, render, Text, useApp, useInput } from 'ink';\nimport SelectInput from 'ink-select-input';\nimport TextInput from 'ink-text-input';\nimport isCI from 'is-ci';\nimport { useState } from 'react';\nimport type { PromptService, SelectOption } from './PromptService.js';\n\n// ---------------------------------------------------------------------------\n// InkPromptService — renders interactive prompts via Ink/React.\n//\n// Auto-detects non-interactive environments (no TTY or CI) and returns the\n// supplied fallback without ever rendering.\n// ---------------------------------------------------------------------------\n\nexport class InkPromptService implements PromptService {\n public constructor(private readonly isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY) && !isCI) {}\n\n public async select(message: string, options: SelectOption[], fallbackValue: string): Promise<string> {\n if (!this.isInteractive) {\n return fallbackValue;\n }\n return await new Promise<string>((resolve) => {\n const app = render(<SelectPrompt message={message} options={options} onSubmit={(value) => resolve(value)} />);\n void Promise.resolve().then(() => app.waitUntilExit());\n });\n }\n\n public async text(message: string, initialValue: string): Promise<string> {\n if (!this.isInteractive) {\n return initialValue;\n }\n return await new Promise<string>((resolve) => {\n const app = render(\n <TextPrompt message={message} initialValue={initialValue} onSubmit={(value) => resolve(value)} />,\n );\n void Promise.resolve().then(() => app.waitUntilExit());\n });\n }\n\n public async secret(message: string): Promise<string> {\n if (!this.isInteractive) {\n throw new Error(`${message} requires an interactive terminal.`);\n }\n return await new Promise<string>((resolve) => {\n const app = render(<SecretPrompt message={message} onSubmit={(value) => resolve(value)} />);\n void Promise.resolve().then(() => app.waitUntilExit());\n });\n }\n}\n\nfunction SecretPrompt({ message, onSubmit }: { message: string; onSubmit: (value: string) => void }) {\n const [value, setValue] = useState('');\n const { exit } = useApp();\n\n useInput((_, key) => {\n if (key.return) {\n onSubmit(value.trim());\n exit();\n }\n });\n\n return (\n <Box flexDirection=\"column\">\n <Text>{message}</Text>\n <TextInput value={value} onChange={setValue} mask=\"*\" />\n </Box>\n );\n}\n\nfunction SelectPrompt({\n message,\n options,\n onSubmit,\n}: {\n message: string;\n options: SelectOption[];\n onSubmit: (value: string) => void;\n}) {\n const { exit } = useApp();\n return (\n <Box flexDirection=\"column\">\n <Text>{message}</Text>\n <SelectInput\n items={options}\n onSelect={(item) => {\n onSubmit(item.value);\n exit();\n }}\n />\n </Box>\n );\n}\n\nfunction TextPrompt({\n message,\n initialValue,\n onSubmit,\n}: {\n message: string;\n initialValue: string;\n onSubmit: (value: string) => void;\n}) {\n const [value, setValue] = useState(initialValue);\n const { exit } = useApp();\n\n useInput((_, key) => {\n if (key.return) {\n onSubmit(value.trim() || initialValue);\n exit();\n }\n });\n\n return (\n <Box flexDirection=\"column\">\n <Text>{message}</Text>\n <TextInput value={value} onChange={setValue} />\n <Text color=\"gray\">Press Enter to confirm.</Text>\n </Box>\n );\n}\n"],"mappings":";;;;;;;;AAcA,IAAa,mBAAb,MAAuD;CACjB;CAApC,AAAO,YAAY,AAAiB,gBAAgB,QAAQ,QAAQ,MAAM,SAAS,QAAQ,OAAO,KAAK,KAAK,CAAC,MAAM;EAA/E;CAAgF;CAEpH,MAAa,OAAO,SAAiB,SAAyB,eAAwC;EACpG,IAAI,CAAC,KAAK,eACR,OAAO;EAET,OAAO,MAAM,IAAI,SAAiB,YAAY;GAC5C,MAAM,MAAM,OAAO,oBAAC,cAAD;IAAuB;IAAkB;IAAS,WAAW,UAAU,QAAQ,KAAK;GAAI,EAAC;GAC5G,AAAK,QAAQ,QAAQ,CAAC,CAAC,WAAW,IAAI,cAAc,CAAC;EACvD,CAAC;CACH;CAEA,MAAa,KAAK,SAAiB,cAAuC;EACxE,IAAI,CAAC,KAAK,eACR,OAAO;EAET,OAAO,MAAM,IAAI,SAAiB,YAAY;GAC5C,MAAM,MAAM,OACV,oBAAC,YAAD;IAAqB;IAAuB;IAAc,WAAW,UAAU,QAAQ,KAAK;GAAI,EAClG;GACA,AAAK,QAAQ,QAAQ,CAAC,CAAC,WAAW,IAAI,cAAc,CAAC;EACvD,CAAC;CACH;CAEA,MAAa,OAAO,SAAkC;EACpD,IAAI,CAAC,KAAK,eACR,MAAM,IAAI,MAAM,GAAG,QAAQ,mCAAmC;EAEhE,OAAO,MAAM,IAAI,SAAiB,YAAY;GAC5C,MAAM,MAAM,OAAO,oBAAC,cAAD;IAAuB;IAAS,WAAW,UAAU,QAAQ,KAAK;GAAI,EAAC;GAC1F,AAAK,QAAQ,QAAQ,CAAC,CAAC,WAAW,IAAI,cAAc,CAAC;EACvD,CAAC;CACH;AACF;AAEA,SAAS,aAAa,EAAE,SAAS,YAAoE;CACnG,MAAM,CAAC,OAAO,YAAY,SAAS,EAAE;CACrC,MAAM,EAAE,SAAS,OAAO;CAExB,UAAU,GAAG,QAAQ;EACnB,IAAI,IAAI,QAAQ;GACd,SAAS,MAAM,KAAK,CAAC;GACrB,KAAK;EACP;CACF,CAAC;CAED,OACE,qBAAC,KAAD;EAAK,eAAc;YAAnB,CACE,oBAAC,MAAD,YAAO,QAAc,IACrB,oBAAC,WAAD;GAAkB;GAAO,UAAU;GAAU,MAAK;EAAK,EACpD;;AAET;AAEA,SAAS,aAAa,EACpB,SACA,SACA,YAKC;CACD,MAAM,EAAE,SAAS,OAAO;CACxB,OACE,qBAAC,KAAD;EAAK,eAAc;YAAnB,CACE,oBAAC,MAAD,YAAO,QAAc,IACrB,oBAAC,aAAD;GACE,OAAO;GACP,WAAW,SAAS;IAClB,SAAS,KAAK,KAAK;IACnB,KAAK;GACP;EACD,EACE;;AAET;AAEA,SAAS,WAAW,EAClB,SACA,cACA,YAKC;CACD,MAAM,CAAC,OAAO,YAAY,SAAS,YAAY;CAC/C,MAAM,EAAE,SAAS,OAAO;CAExB,UAAU,GAAG,QAAQ;EACnB,IAAI,IAAI,QAAQ;GACd,SAAS,MAAM,KAAK,KAAK,YAAY;GACrC,KAAK;EACP;CACF,CAAC;CAED,OACE,qBAAC,KAAD;EAAK,eAAc;YAAnB;GACE,oBAAC,MAAD,YAAO,QAAc;GACrB,oBAAC,WAAD;IAAkB;IAAO,UAAU;GAAW;GAC9C,oBAAC,MAAD;IAAM,OAAM;cAAO;GAA6B;EAC7C;;AAET"}
1
+ {"version":3,"file":"InkPromptService.js","names":[],"sources":["../../src/prompts/InkPromptService.tsx"],"sourcesContent":["import { Box, render, Text, useApp, useInput } from 'ink';\nimport SelectInput from 'ink-select-input';\nimport TextInput from 'ink-text-input';\nimport isCI from 'is-ci';\nimport { useState } from 'react';\nimport type { PromptService, SelectOption } from '#cli/prompts/PromptService.ts';\n\n// ---------------------------------------------------------------------------\n// InkPromptService — renders interactive prompts via Ink/React.\n//\n// Auto-detects non-interactive environments (no TTY or CI) and returns the\n// supplied fallback without ever rendering.\n// ---------------------------------------------------------------------------\n\nexport class InkPromptService implements PromptService {\n public constructor(private readonly isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY) && !isCI) {}\n\n public async select(message: string, options: SelectOption[], fallbackValue: string): Promise<string> {\n if (!this.isInteractive) {\n return fallbackValue;\n }\n return await new Promise<string>((resolve) => {\n const app = render(<SelectPrompt message={message} options={options} onSubmit={(value) => resolve(value)} />);\n void Promise.resolve().then(() => app.waitUntilExit());\n });\n }\n\n public async text(message: string, initialValue: string): Promise<string> {\n if (!this.isInteractive) {\n return initialValue;\n }\n return await new Promise<string>((resolve) => {\n const app = render(\n <TextPrompt message={message} initialValue={initialValue} onSubmit={(value) => resolve(value)} />,\n );\n void Promise.resolve().then(() => app.waitUntilExit());\n });\n }\n\n public async secret(message: string): Promise<string> {\n if (!this.isInteractive) {\n throw new Error(`${message} requires an interactive terminal.`);\n }\n return await new Promise<string>((resolve) => {\n const app = render(<SecretPrompt message={message} onSubmit={(value) => resolve(value)} />);\n void Promise.resolve().then(() => app.waitUntilExit());\n });\n }\n}\n\nfunction SecretPrompt({ message, onSubmit }: { message: string; onSubmit: (value: string) => void }) {\n const [value, setValue] = useState('');\n const { exit } = useApp();\n\n useInput((_, key) => {\n if (key.return) {\n onSubmit(value.trim());\n exit();\n }\n });\n\n return (\n <Box flexDirection=\"column\">\n <Text>{message}</Text>\n <TextInput value={value} onChange={setValue} mask=\"*\" />\n </Box>\n );\n}\n\nfunction SelectPrompt({\n message,\n options,\n onSubmit,\n}: {\n message: string;\n options: SelectOption[];\n onSubmit: (value: string) => void;\n}) {\n const { exit } = useApp();\n return (\n <Box flexDirection=\"column\">\n <Text>{message}</Text>\n <SelectInput\n items={options}\n onSelect={(item) => {\n onSubmit(item.value);\n exit();\n }}\n />\n </Box>\n );\n}\n\nfunction TextPrompt({\n message,\n initialValue,\n onSubmit,\n}: {\n message: string;\n initialValue: string;\n onSubmit: (value: string) => void;\n}) {\n const [value, setValue] = useState(initialValue);\n const { exit } = useApp();\n\n useInput((_, key) => {\n if (key.return) {\n onSubmit(value.trim() || initialValue);\n exit();\n }\n });\n\n return (\n <Box flexDirection=\"column\">\n <Text>{message}</Text>\n <TextInput value={value} onChange={setValue} />\n <Text color=\"gray\">Press Enter to confirm.</Text>\n </Box>\n );\n}\n"],"mappings":";;;;;;;;AAcA,IAAa,mBAAb,MAAuD;CACjB;CAApC,AAAO,YAAY,AAAiB,gBAAgB,QAAQ,QAAQ,MAAM,SAAS,QAAQ,OAAO,KAAK,KAAK,CAAC,MAAM;EAA/E;CAAgF;CAEpH,MAAa,OAAO,SAAiB,SAAyB,eAAwC;EACpG,IAAI,CAAC,KAAK,eACR,OAAO;EAET,OAAO,MAAM,IAAI,SAAiB,YAAY;GAC5C,MAAM,MAAM,OAAO,oBAAC,cAAD;IAAuB;IAAkB;IAAS,WAAW,UAAU,QAAQ,KAAK;GAAI,EAAC;GAC5G,AAAK,QAAQ,QAAQ,CAAC,CAAC,WAAW,IAAI,cAAc,CAAC;EACvD,CAAC;CACH;CAEA,MAAa,KAAK,SAAiB,cAAuC;EACxE,IAAI,CAAC,KAAK,eACR,OAAO;EAET,OAAO,MAAM,IAAI,SAAiB,YAAY;GAC5C,MAAM,MAAM,OACV,oBAAC,YAAD;IAAqB;IAAuB;IAAc,WAAW,UAAU,QAAQ,KAAK;GAAI,EAClG;GACA,AAAK,QAAQ,QAAQ,CAAC,CAAC,WAAW,IAAI,cAAc,CAAC;EACvD,CAAC;CACH;CAEA,MAAa,OAAO,SAAkC;EACpD,IAAI,CAAC,KAAK,eACR,MAAM,IAAI,MAAM,GAAG,QAAQ,mCAAmC;EAEhE,OAAO,MAAM,IAAI,SAAiB,YAAY;GAC5C,MAAM,MAAM,OAAO,oBAAC,cAAD;IAAuB;IAAS,WAAW,UAAU,QAAQ,KAAK;GAAI,EAAC;GAC1F,AAAK,QAAQ,QAAQ,CAAC,CAAC,WAAW,IAAI,cAAc,CAAC;EACvD,CAAC;CACH;AACF;AAEA,SAAS,aAAa,EAAE,SAAS,YAAoE;CACnG,MAAM,CAAC,OAAO,YAAY,SAAS,EAAE;CACrC,MAAM,EAAE,SAAS,OAAO;CAExB,UAAU,GAAG,QAAQ;EACnB,IAAI,IAAI,QAAQ;GACd,SAAS,MAAM,KAAK,CAAC;GACrB,KAAK;EACP;CACF,CAAC;CAED,OACE,qBAAC,KAAD;EAAK,eAAc;YAAnB,CACE,oBAAC,MAAD,YAAO,QAAc,IACrB,oBAAC,WAAD;GAAkB;GAAO,UAAU;GAAU,MAAK;EAAK,EACpD;;AAET;AAEA,SAAS,aAAa,EACpB,SACA,SACA,YAKC;CACD,MAAM,EAAE,SAAS,OAAO;CACxB,OACE,qBAAC,KAAD;EAAK,eAAc;YAAnB,CACE,oBAAC,MAAD,YAAO,QAAc,IACrB,oBAAC,aAAD;GACE,OAAO;GACP,WAAW,SAAS;IAClB,SAAS,KAAK,KAAK;IACnB,KAAK;GACP;EACD,EACE;;AAET;AAEA,SAAS,WAAW,EAClB,SACA,cACA,YAKC;CACD,MAAM,CAAC,OAAO,YAAY,SAAS,YAAY;CAC/C,MAAM,EAAE,SAAS,OAAO;CAExB,UAAU,GAAG,QAAQ;EACnB,IAAI,IAAI,QAAQ;GACd,SAAS,MAAM,KAAK,KAAK,YAAY;GACrC,KAAK;EACP;CACF,CAAC;CAED,OACE,qBAAC,KAAD;EAAK,eAAc;YAAnB;GACE,oBAAC,MAAD,YAAO,QAAc;GACrB,oBAAC,WAAD;IAAkB;IAAO,UAAU;GAAW;GAC9C,oBAAC,MAAD;IAAM,OAAM;cAAO;GAA6B;EAC7C;;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"DiagnosticReporter.js","names":[],"sources":["../../src/reporting/DiagnosticReporter.ts"],"sourcesContent":["import type { Diagnostic } from '@uapkg/diagnostics';\nimport { DiagnosticDeduplicator } from './DiagnosticDeduplicator.js';\nimport type { DiagnosticRenderer } from './DiagnosticRenderer.js';\nimport { InkDiagnosticRenderer } from './InkDiagnosticRenderer.js';\nimport { sortDiagnostics } from './TextSink.js';\n\n/**\n * Human-readable diagnostic reporter.\n *\n * Routing policy: `error` → stderr, `warning` / `info` → stdout. Output\n * rendering is delegated to an injected {@link DiagnosticRenderer} so this\n * class is UI-agnostic.\n *\n * Defaults to the Ink renderer (styled, colored output); tests typically\n * inject {@link TextDiagnosticRenderer}.\n *\n * One instance is safe to reuse across commands in a single CLI run.\n */\nexport class DiagnosticReporter {\n public constructor(\n private readonly renderer: DiagnosticRenderer = new InkDiagnosticRenderer(),\n private readonly deduplicator: DiagnosticDeduplicator = new DiagnosticDeduplicator(),\n ) {}\n\n public reportAll(diagnostics: readonly Diagnostic[]): void {\n if (diagnostics.length === 0) return;\n const deduped = this.deduplicator.filter(diagnostics);\n if (deduped.length === 0) return;\n const sorted = sortDiagnostics(deduped);\n const errors = sorted.filter((d) => d.level === 'error');\n const others = sorted.filter((d) => d.level !== 'error');\n if (errors.length > 0) this.renderer.render(errors, 'stderr');\n if (others.length > 0) this.renderer.render(others, 'stdout');\n }\n\n public reportOne(diagnostic: Diagnostic): void {\n this.reportAll([diagnostic]);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAkBA,IAAa,qBAAb,MAAgC;CAEX;CACA;CAFnB,AAAO,YACL,AAAiB,WAA+B,IAAI,sBAAsB,GAC1E,AAAiB,eAAuC,IAAI,uBAAuB,GACnF;EAFiB;EACA;CAChB;CAEH,AAAO,UAAU,aAA0C;EACzD,IAAI,YAAY,WAAW,GAAG;EAC9B,MAAM,UAAU,KAAK,aAAa,OAAO,WAAW;EACpD,IAAI,QAAQ,WAAW,GAAG;EAC1B,MAAM,SAAS,gBAAgB,OAAO;EACtC,MAAM,SAAS,OAAO,QAAQ,MAAM,EAAE,UAAU,OAAO;EACvD,MAAM,SAAS,OAAO,QAAQ,MAAM,EAAE,UAAU,OAAO;EACvD,IAAI,OAAO,SAAS,GAAG,KAAK,SAAS,OAAO,QAAQ,QAAQ;EAC5D,IAAI,OAAO,SAAS,GAAG,KAAK,SAAS,OAAO,QAAQ,QAAQ;CAC9D;CAEA,AAAO,UAAU,YAA8B;EAC7C,KAAK,UAAU,CAAC,UAAU,CAAC;CAC7B;AACF"}
1
+ {"version":3,"file":"DiagnosticReporter.js","names":[],"sources":["../../src/reporting/DiagnosticReporter.ts"],"sourcesContent":["import type { Diagnostic } from '@uapkg/diagnostics';\nimport { DiagnosticDeduplicator } from '#cli/reporting/DiagnosticDeduplicator.ts';\nimport type { DiagnosticRenderer } from '#cli/reporting/DiagnosticRenderer.ts';\nimport { InkDiagnosticRenderer } from '#cli/reporting/InkDiagnosticRenderer.ts';\nimport { sortDiagnostics } from '#cli/reporting/TextSink.ts';\n\n/**\n * Human-readable diagnostic reporter.\n *\n * Routing policy: `error` → stderr, `warning` / `info` → stdout. Output\n * rendering is delegated to an injected {@link DiagnosticRenderer} so this\n * class is UI-agnostic.\n *\n * Defaults to the Ink renderer (styled, colored output); tests typically\n * inject {@link TextDiagnosticRenderer}.\n *\n * One instance is safe to reuse across commands in a single CLI run.\n */\nexport class DiagnosticReporter {\n public constructor(\n private readonly renderer: DiagnosticRenderer = new InkDiagnosticRenderer(),\n private readonly deduplicator: DiagnosticDeduplicator = new DiagnosticDeduplicator(),\n ) {}\n\n public reportAll(diagnostics: readonly Diagnostic[]): void {\n if (diagnostics.length === 0) return;\n const deduped = this.deduplicator.filter(diagnostics);\n if (deduped.length === 0) return;\n const sorted = sortDiagnostics(deduped);\n const errors = sorted.filter((d) => d.level === 'error');\n const others = sorted.filter((d) => d.level !== 'error');\n if (errors.length > 0) this.renderer.render(errors, 'stderr');\n if (others.length > 0) this.renderer.render(others, 'stdout');\n }\n\n public reportOne(diagnostic: Diagnostic): void {\n this.reportAll([diagnostic]);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAkBA,IAAa,qBAAb,MAAgC;CAEX;CACA;CAFnB,AAAO,YACL,AAAiB,WAA+B,IAAI,sBAAsB,GAC1E,AAAiB,eAAuC,IAAI,uBAAuB,GACnF;EAFiB;EACA;CAChB;CAEH,AAAO,UAAU,aAA0C;EACzD,IAAI,YAAY,WAAW,GAAG;EAC9B,MAAM,UAAU,KAAK,aAAa,OAAO,WAAW;EACpD,IAAI,QAAQ,WAAW,GAAG;EAC1B,MAAM,SAAS,gBAAgB,OAAO;EACtC,MAAM,SAAS,OAAO,QAAQ,MAAM,EAAE,UAAU,OAAO;EACvD,MAAM,SAAS,OAAO,QAAQ,MAAM,EAAE,UAAU,OAAO;EACvD,IAAI,OAAO,SAAS,GAAG,KAAK,SAAS,OAAO,QAAQ,QAAQ;EAC5D,IAAI,OAAO,SAAS,GAAG,KAAK,SAAS,OAAO,QAAQ,QAAQ;CAC9D;CAEA,AAAO,UAAU,YAA8B;EAC7C,KAAK,UAAU,CAAC,UAAU,CAAC;CAC7B;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"InkDiagnosticRenderer.js","names":[],"sources":["../../src/reporting/InkDiagnosticRenderer.ts"],"sourcesContent":["import type { Diagnostic } from '@uapkg/diagnostics';\nimport { formatPublishRequestFailed } from '@uapkg/diagnostics-format';\nimport {\n createInkRegistry,\n type DiagnosticInkRegistry,\n DiagnosticsListView,\n defaultInkComponents,\n} from '@uapkg/diagnostics-format/ink';\nimport { render } from 'ink';\nimport { createElement } from 'react';\nimport type { DiagnosticRenderer } from './DiagnosticRenderer.js';\nimport type { TextSink } from './TextSink.js';\n\n/**\n * Ink-backed diagnostic renderer.\n *\n * Renders each batch as a static Ink tree to the target process stream. No\n * mount loop, no animations — one-shot render → unmount, so the process can\n * exit immediately after the CLI command finishes.\n *\n * On any Ink failure (missing terminfo, exotic stream, etc.) the renderer\n * delegates to a fallback text sink so diagnostics are never lost.\n */\nexport class InkDiagnosticRenderer implements DiagnosticRenderer {\n public constructor(\n private readonly registry: DiagnosticInkRegistry = createInkRegistry(defaultInkComponents),\n private readonly fallback?: TextSink,\n ) {}\n\n public render(diagnostics: readonly Diagnostic[], stream: 'stdout' | 'stderr'): void {\n if (diagnostics.length === 0) return;\n const target = stream === 'stderr' ? process.stderr : process.stdout;\n try {\n const element = createElement(DiagnosticsListView, {\n diagnostics,\n registry: this.registry,\n });\n const instance = render(element, { stdout: target, patchConsole: false });\n instance.unmount();\n } catch {\n if (!this.fallback) return;\n for (const diagnostic of diagnostics) {\n if (diagnostic.code === 'PUBLISH_REQUEST_FAILED') {\n for (const line of formatPublishRequestFailed(diagnostic).split('\\n')) this.fallback.writeLine(line);\n continue;\n }\n this.fallback.writeLine(`[${diagnostic.level}] ${diagnostic.code}: ${diagnostic.message}`);\n if (diagnostic.hint) this.fallback.writeLine(` → ${diagnostic.hint}`);\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAuBA,IAAa,wBAAb,MAAiE;CAE5C;CACA;CAFnB,AAAO,YACL,AAAiB,WAAkC,kBAAkB,oBAAoB,GACzF,AAAiB,UACjB;EAFiB;EACA;CAChB;CAEH,AAAO,OAAO,aAAoC,QAAmC;EACnF,IAAI,YAAY,WAAW,GAAG;EAC9B,MAAM,SAAS,WAAW,WAAW,QAAQ,SAAS,QAAQ;EAC9D,IAAI;GACF,MAAM,UAAU,cAAc,qBAAqB;IACjD;IACA,UAAU,KAAK;GACjB,CAAC;GAED,AADiB,OAAO,SAAS;IAAE,QAAQ;IAAQ,cAAc;GAAM,CAChE,CAAC,CAAC,QAAQ;EACnB,QAAQ;GACN,IAAI,CAAC,KAAK,UAAU;GACpB,KAAK,MAAM,cAAc,aAAa;IACpC,IAAI,WAAW,SAAS,0BAA0B;KAChD,KAAK,MAAM,QAAQ,2BAA2B,UAAU,CAAC,CAAC,MAAM,IAAI,GAAG,KAAK,SAAS,UAAU,IAAI;KACnG;IACF;IACA,KAAK,SAAS,UAAU,IAAI,WAAW,MAAM,IAAI,WAAW,KAAK,IAAI,WAAW,SAAS;IACzF,IAAI,WAAW,MAAM,KAAK,SAAS,UAAU,OAAO,WAAW,MAAM;GACvE;EACF;CACF;AACF"}
1
+ {"version":3,"file":"InkDiagnosticRenderer.js","names":[],"sources":["../../src/reporting/InkDiagnosticRenderer.ts"],"sourcesContent":["import type { Diagnostic } from '@uapkg/diagnostics';\nimport { formatPublishRequestFailed } from '@uapkg/diagnostics-format';\nimport {\n createInkRegistry,\n type DiagnosticInkRegistry,\n DiagnosticsListView,\n defaultInkComponents,\n} from '@uapkg/diagnostics-format/ink';\nimport { render } from 'ink';\nimport { createElement } from 'react';\nimport type { DiagnosticRenderer } from '#cli/reporting/DiagnosticRenderer.ts';\nimport type { TextSink } from '#cli/reporting/TextSink.ts';\n\n/**\n * Ink-backed diagnostic renderer.\n *\n * Renders each batch as a static Ink tree to the target process stream. No\n * mount loop, no animations — one-shot render → unmount, so the process can\n * exit immediately after the CLI command finishes.\n *\n * On any Ink failure (missing terminfo, exotic stream, etc.) the renderer\n * delegates to a fallback text sink so diagnostics are never lost.\n */\nexport class InkDiagnosticRenderer implements DiagnosticRenderer {\n public constructor(\n private readonly registry: DiagnosticInkRegistry = createInkRegistry(defaultInkComponents),\n private readonly fallback?: TextSink,\n ) {}\n\n public render(diagnostics: readonly Diagnostic[], stream: 'stdout' | 'stderr'): void {\n if (diagnostics.length === 0) return;\n const target = stream === 'stderr' ? process.stderr : process.stdout;\n try {\n const element = createElement(DiagnosticsListView, {\n diagnostics,\n registry: this.registry,\n });\n const instance = render(element, { stdout: target, patchConsole: false });\n instance.unmount();\n } catch {\n if (!this.fallback) return;\n for (const diagnostic of diagnostics) {\n if (diagnostic.code === 'PUBLISH_REQUEST_FAILED') {\n for (const line of formatPublishRequestFailed(diagnostic).split('\\n')) this.fallback.writeLine(line);\n continue;\n }\n this.fallback.writeLine(`[${diagnostic.level}] ${diagnostic.code}: ${diagnostic.message}`);\n if (diagnostic.hint) this.fallback.writeLine(` → ${diagnostic.hint}`);\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAuBA,IAAa,wBAAb,MAAiE;CAE5C;CACA;CAFnB,AAAO,YACL,AAAiB,WAAkC,kBAAkB,oBAAoB,GACzF,AAAiB,UACjB;EAFiB;EACA;CAChB;CAEH,AAAO,OAAO,aAAoC,QAAmC;EACnF,IAAI,YAAY,WAAW,GAAG;EAC9B,MAAM,SAAS,WAAW,WAAW,QAAQ,SAAS,QAAQ;EAC9D,IAAI;GACF,MAAM,UAAU,cAAc,qBAAqB;IACjD;IACA,UAAU,KAAK;GACjB,CAAC;GAED,AADiB,OAAO,SAAS;IAAE,QAAQ;IAAQ,cAAc;GAAM,CAChE,CAAC,CAAC,QAAQ;EACnB,QAAQ;GACN,IAAI,CAAC,KAAK,UAAU;GACpB,KAAK,MAAM,cAAc,aAAa;IACpC,IAAI,WAAW,SAAS,0BAA0B;KAChD,KAAK,MAAM,QAAQ,2BAA2B,UAAU,CAAC,CAAC,MAAM,IAAI,GAAG,KAAK,SAAS,UAAU,IAAI;KACnG;IACF;IACA,KAAK,SAAS,UAAU,IAAI,WAAW,MAAM,IAAI,WAAW,KAAK,IAAI,WAAW,SAAS;IACzF,IAAI,WAAW,MAAM,KAAK,SAAS,UAAU,OAAO,WAAW,MAAM;GACvE;EACF;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"InstallProgressReporter.js","names":[],"sources":["../../src/reporting/InstallProgressReporter.ts"],"sourcesContent":["import { prettyBytes } from '@uapkg/common';\nimport type { DownloadStatusSnapshot } from '@uapkg/installer';\nimport { ProcessTextSink, type TextSink } from './TextSink.js';\n\n/**\n * Text-mode progress reporter for `uapkg install / add / update`.\n *\n * Subscribes to the installer's `AsyncIterable<DownloadStatusSnapshot>` and\n * writes a one-line summary whenever the *aggregate totals* change. This keeps\n * output readable even when stdout is piped or captured (no ANSI cursor\n * tricks). Per-slot rows are intentionally omitted in the MVP — the full Ink\n * `InstallProgress` grid is a planned follow-up.\n *\n * At completion, emits a final summary line:\n * `N added, M updated, X removed, Y unchanged (B bytes)`\n */\nexport class InstallProgressReporter {\n private lastBytesDone = -1;\n private lastState = '';\n\n public constructor(private readonly sink: TextSink = new ProcessTextSink(process.stdout)) {}\n\n public async consume(stream: AsyncIterable<DownloadStatusSnapshot>): Promise<void> {\n for await (const snapshot of stream) {\n this.renderTick(snapshot);\n }\n }\n\n private renderTick(snapshot: DownloadStatusSnapshot): void {\n const { totals, slots } = snapshot;\n const active = slots.filter(\n (s) => s.state === 'downloading' || s.state === 'verifying' || s.state === 'extracting' || s.state === 'removing',\n ).length;\n\n const stateKey = `${active}|${totals.bytesDone}|${totals.bytesTotal}`;\n if (stateKey === this.lastState) return;\n this.lastState = stateKey;\n\n // Only emit a byte-progress line when bytesTotal is known and has advanced.\n if (totals.bytesTotal > 0 && totals.bytesDone !== this.lastBytesDone) {\n this.lastBytesDone = totals.bytesDone;\n const pct = Math.min(100, Math.floor((totals.bytesDone / totals.bytesTotal) * 100));\n this.sink.writeLine(\n ` installing: ${pct}% (${prettyBytes(totals.bytesDone)} / ${prettyBytes(totals.bytesTotal)}) — ${active} active`,\n );\n }\n }\n\n /**\n * Emit the post-install one-liner. The caller passes the installer's plan\n * summary directly; this class does not retain it.\n */\n public renderSummary(summary: {\n added: number;\n updated: number;\n removed: number;\n unchanged: number;\n totalBytes: number;\n }): void {\n this.sink.writeLine(\n `${summary.added} added, ${summary.updated} updated, ${summary.removed} removed, ${summary.unchanged} unchanged (${prettyBytes(summary.totalBytes)})`,\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,IAAa,0BAAb,MAAqC;CAIC;CAHpC,AAAQ,gBAAgB;CACxB,AAAQ,YAAY;CAEpB,AAAO,YAAY,AAAiB,OAAiB,IAAI,gBAAgB,QAAQ,MAAM,GAAG;EAAtD;CAAuD;CAE3F,MAAa,QAAQ,QAA8D;EACjF,WAAW,MAAM,YAAY,QAC3B,KAAK,WAAW,QAAQ;CAE5B;CAEA,AAAQ,WAAW,UAAwC;EACzD,MAAM,EAAE,QAAQ,UAAU;EAC1B,MAAM,SAAS,MAAM,QAClB,MAAM,EAAE,UAAU,iBAAiB,EAAE,UAAU,eAAe,EAAE,UAAU,gBAAgB,EAAE,UAAU,UACzG,CAAC,CAAC;EAEF,MAAM,WAAW,GAAG,OAAO,GAAG,OAAO,UAAU,GAAG,OAAO;EACzD,IAAI,aAAa,KAAK,WAAW;EACjC,KAAK,YAAY;EAGjB,IAAI,OAAO,aAAa,KAAK,OAAO,cAAc,KAAK,eAAe;GACpE,KAAK,gBAAgB,OAAO;GAC5B,MAAM,MAAM,KAAK,IAAI,KAAK,KAAK,MAAO,OAAO,YAAY,OAAO,aAAc,GAAG,CAAC;GAClF,KAAK,KAAK,UACR,iBAAiB,IAAI,KAAK,YAAY,OAAO,SAAS,EAAE,KAAK,YAAY,OAAO,UAAU,EAAE,MAAM,OAAO,QAC3G;EACF;CACF;;;;;CAMA,AAAO,cAAc,SAMZ;EACP,KAAK,KAAK,UACR,GAAG,QAAQ,MAAM,UAAU,QAAQ,QAAQ,YAAY,QAAQ,QAAQ,YAAY,QAAQ,UAAU,cAAc,YAAY,QAAQ,UAAU,EAAE,EACrJ;CACF;AACF"}
1
+ {"version":3,"file":"InstallProgressReporter.js","names":[],"sources":["../../src/reporting/InstallProgressReporter.ts"],"sourcesContent":["import { prettyBytes } from '@uapkg/common';\nimport type { DownloadStatusSnapshot } from '@uapkg/installer';\nimport { ProcessTextSink, type TextSink } from '#cli/reporting/TextSink.ts';\n\n/**\n * Text-mode progress reporter for `uapkg install / add / update`.\n *\n * Subscribes to the installer's `AsyncIterable<DownloadStatusSnapshot>` and\n * writes a one-line summary whenever the *aggregate totals* change. This keeps\n * output readable even when stdout is piped or captured (no ANSI cursor\n * tricks). Per-slot rows are intentionally omitted in the MVP — the full Ink\n * `InstallProgress` grid is a planned follow-up.\n *\n * At completion, emits a final summary line:\n * `N added, M updated, X removed, Y unchanged (B bytes)`\n */\nexport class InstallProgressReporter {\n private lastBytesDone = -1;\n private lastState = '';\n\n public constructor(private readonly sink: TextSink = new ProcessTextSink(process.stdout)) {}\n\n public async consume(stream: AsyncIterable<DownloadStatusSnapshot>): Promise<void> {\n for await (const snapshot of stream) {\n this.renderTick(snapshot);\n }\n }\n\n private renderTick(snapshot: DownloadStatusSnapshot): void {\n const { totals, slots } = snapshot;\n const active = slots.filter(\n (s) => s.state === 'downloading' || s.state === 'verifying' || s.state === 'extracting' || s.state === 'removing',\n ).length;\n\n const stateKey = `${active}|${totals.bytesDone}|${totals.bytesTotal}`;\n if (stateKey === this.lastState) return;\n this.lastState = stateKey;\n\n // Only emit a byte-progress line when bytesTotal is known and has advanced.\n if (totals.bytesTotal > 0 && totals.bytesDone !== this.lastBytesDone) {\n this.lastBytesDone = totals.bytesDone;\n const pct = Math.min(100, Math.floor((totals.bytesDone / totals.bytesTotal) * 100));\n this.sink.writeLine(\n ` installing: ${pct}% (${prettyBytes(totals.bytesDone)} / ${prettyBytes(totals.bytesTotal)}) — ${active} active`,\n );\n }\n }\n\n /**\n * Emit the post-install one-liner. The caller passes the installer's plan\n * summary directly; this class does not retain it.\n */\n public renderSummary(summary: {\n added: number;\n updated: number;\n removed: number;\n unchanged: number;\n totalBytes: number;\n }): void {\n this.sink.writeLine(\n `${summary.added} added, ${summary.updated} updated, ${summary.removed} removed, ${summary.unchanged} unchanged (${prettyBytes(summary.totalBytes)})`,\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,IAAa,0BAAb,MAAqC;CAIC;CAHpC,AAAQ,gBAAgB;CACxB,AAAQ,YAAY;CAEpB,AAAO,YAAY,AAAiB,OAAiB,IAAI,gBAAgB,QAAQ,MAAM,GAAG;EAAtD;CAAuD;CAE3F,MAAa,QAAQ,QAA8D;EACjF,WAAW,MAAM,YAAY,QAC3B,KAAK,WAAW,QAAQ;CAE5B;CAEA,AAAQ,WAAW,UAAwC;EACzD,MAAM,EAAE,QAAQ,UAAU;EAC1B,MAAM,SAAS,MAAM,QAClB,MAAM,EAAE,UAAU,iBAAiB,EAAE,UAAU,eAAe,EAAE,UAAU,gBAAgB,EAAE,UAAU,UACzG,CAAC,CAAC;EAEF,MAAM,WAAW,GAAG,OAAO,GAAG,OAAO,UAAU,GAAG,OAAO;EACzD,IAAI,aAAa,KAAK,WAAW;EACjC,KAAK,YAAY;EAGjB,IAAI,OAAO,aAAa,KAAK,OAAO,cAAc,KAAK,eAAe;GACpE,KAAK,gBAAgB,OAAO;GAC5B,MAAM,MAAM,KAAK,IAAI,KAAK,KAAK,MAAO,OAAO,YAAY,OAAO,aAAc,GAAG,CAAC;GAClF,KAAK,KAAK,UACR,iBAAiB,IAAI,KAAK,YAAY,OAAO,SAAS,EAAE,KAAK,YAAY,OAAO,UAAU,EAAE,MAAM,OAAO,QAC3G;EACF;CACF;;;;;CAMA,AAAO,cAAc,SAMZ;EACP,KAAK,KAAK,UACR,GAAG,QAAQ,MAAM,UAAU,QAAQ,QAAQ,YAAY,QAAQ,QAAQ,YAAY,QAAQ,UAAU,cAAc,YAAY,QAAQ,UAAU,EAAE,EACrJ;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"JsonReporter.js","names":[],"sources":["../../src/reporting/JsonReporter.ts"],"sourcesContent":["import type { Diagnostic } from '@uapkg/diagnostics';\nimport { ProcessTextSink, type TextSink } from './TextSink.js';\n\n/**\n * Stable JSON envelope used when any command is invoked with `--json`.\n *\n * `status` is `ok` / `error` so scripts can branch on exit-independent\n * outcomes. `diagnostics` always appears (empty array when none), so\n * consumers never need to handle undefined.\n */\nexport interface JsonEnvelope<TData = unknown> {\n readonly status: 'ok' | 'error';\n readonly command: string;\n readonly data?: TData;\n readonly diagnostics: readonly Diagnostic[];\n}\n\n/**\n * Writes a single JSON document to stdout per invocation. Never writes\n * progress or partial output — callers accumulate everything into a payload\n * and call `emit()` once at the end of a command.\n */\nexport class JsonReporter {\n public constructor(private readonly sink: TextSink = new ProcessTextSink(process.stdout)) {}\n\n public emitSuccess<TData>(command: string, data: TData, diagnostics: readonly Diagnostic[] = []): void {\n this.emit({ status: 'ok', command, data, diagnostics });\n }\n\n public emitError(command: string, diagnostics: readonly Diagnostic[]): void {\n this.emit({ status: 'error', command, diagnostics });\n }\n\n public emit<TData>(envelope: JsonEnvelope<TData>): void {\n this.sink.writeLine(JSON.stringify(envelope, null, 2));\n }\n}\n"],"mappings":";;;;;;;;AAsBA,IAAa,eAAb,MAA0B;CACY;CAApC,AAAO,YAAY,AAAiB,OAAiB,IAAI,gBAAgB,QAAQ,MAAM,GAAG;EAAtD;CAAuD;CAE3F,AAAO,YAAmB,SAAiB,MAAa,cAAqC,CAAC,GAAS;EACrG,KAAK,KAAK;GAAE,QAAQ;GAAM;GAAS;GAAM;EAAY,CAAC;CACxD;CAEA,AAAO,UAAU,SAAiB,aAA0C;EAC1E,KAAK,KAAK;GAAE,QAAQ;GAAS;GAAS;EAAY,CAAC;CACrD;CAEA,AAAO,KAAY,UAAqC;EACtD,KAAK,KAAK,UAAU,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;CACvD;AACF"}
1
+ {"version":3,"file":"JsonReporter.js","names":[],"sources":["../../src/reporting/JsonReporter.ts"],"sourcesContent":["import type { Diagnostic } from '@uapkg/diagnostics';\nimport { ProcessTextSink, type TextSink } from '#cli/reporting/TextSink.ts';\n\n/**\n * Stable JSON envelope used when any command is invoked with `--json`.\n *\n * `status` is `ok` / `error` so scripts can branch on exit-independent\n * outcomes. `diagnostics` always appears (empty array when none), so\n * consumers never need to handle undefined.\n */\nexport interface JsonEnvelope<TData = unknown> {\n readonly status: 'ok' | 'error';\n readonly command: string;\n readonly data?: TData;\n readonly diagnostics: readonly Diagnostic[];\n}\n\n/**\n * Writes a single JSON document to stdout per invocation. Never writes\n * progress or partial output — callers accumulate everything into a payload\n * and call `emit()` once at the end of a command.\n */\nexport class JsonReporter {\n public constructor(private readonly sink: TextSink = new ProcessTextSink(process.stdout)) {}\n\n public emitSuccess<TData>(command: string, data: TData, diagnostics: readonly Diagnostic[] = []): void {\n this.emit({ status: 'ok', command, data, diagnostics });\n }\n\n public emitError(command: string, diagnostics: readonly Diagnostic[]): void {\n this.emit({ status: 'error', command, diagnostics });\n }\n\n public emit<TData>(envelope: JsonEnvelope<TData>): void {\n this.sink.writeLine(JSON.stringify(envelope, null, 2));\n }\n}\n"],"mappings":";;;;;;;;AAsBA,IAAa,eAAb,MAA0B;CACY;CAApC,AAAO,YAAY,AAAiB,OAAiB,IAAI,gBAAgB,QAAQ,MAAM,GAAG;EAAtD;CAAuD;CAE3F,AAAO,YAAmB,SAAiB,MAAa,cAAqC,CAAC,GAAS;EACrG,KAAK,KAAK;GAAE,QAAQ;GAAM;GAAS;GAAM;EAAY,CAAC;CACxD;CAEA,AAAO,UAAU,SAAiB,aAA0C;EAC1E,KAAK,KAAK;GAAE,QAAQ;GAAS;GAAS;EAAY,CAAC;CACrD;CAEA,AAAO,KAAY,UAAqC;EACtD,KAAK,KAAK,UAAU,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;CACvD;AACF"}
@@ -61,7 +61,7 @@ function formatText(options) {
61
61
  function presentationLines(presentation, request) {
62
62
  if (presentation.kind === "publish") {
63
63
  const lines = [`Publishing request ${request.id}: ${request.status}.`];
64
- if (request.status === "ready_superseded") lines.push("The publication was accepted; a newer change to the same package is already projected.");
64
+ if (request.status === "ready_superseded") lines.push("The publication was accepted; the Git registry already includes a newer change to this package.");
65
65
  return lines;
66
66
  }
67
67
  if (presentation.kind === "lifecycle") return [`${presentation.operation} request ${request.id} for ${presentation.packageName}@${presentation.packageVersion}: ${request.status}.`];
@@ -1 +1 @@
1
- {"version":3,"file":"RegistryRequestTerminalFormatter.js","names":[],"sources":["../../src/reporting/RegistryRequestTerminalFormatter.ts"],"sourcesContent":["import type { UAPKGLifecycleCommandName, UAPKGOutputFormat } from '../cli/UAPKGCommandLine.js';\nimport type {\n RegistryRequestDetail,\n RegistryRequestStatus,\n RegistryRequestSummary,\n} from '../control-plane/ControlPlaneTypes.js';\n\nconst TERMINAL_STATUSES: ReadonlySet<RegistryRequestStatus> = new Set([\n 'ready',\n 'ready_superseded',\n 'rejected',\n 'operationally_failed',\n]);\nconst SUCCESS_STATUSES: ReadonlySet<RegistryRequestStatus> = new Set(['ready', 'ready_superseded']);\n\nexport type RegistryRequestPresentation =\n | { readonly kind: 'publish' }\n | {\n readonly kind: 'lifecycle';\n readonly operation: UAPKGLifecycleCommandName;\n readonly packageName: string;\n readonly packageVersion: string;\n }\n | { readonly kind: 'status' };\n\nexport interface RegistryRequestTerminalFormatOptions {\n readonly detail: RegistryRequestDetail;\n readonly registryAlias: string;\n readonly outputFormat: UAPKGOutputFormat;\n readonly presentation: RegistryRequestPresentation;\n readonly registryRefreshWarning?: string;\n}\n\nexport interface RegistryRequestTerminalOutput {\n readonly stdout: string;\n readonly stderr?: string;\n}\n\n/**\n * Formats the caller-visible terminal story consistently for publish,\n * lifecycle, and standalone request-status commands. The additive check and\n * failure fields have already been bounded by\n * `ControlPlaneClient.getRegistryRequestDetail` before reaching this boundary.\n */\nexport function formatRegistryRequestTerminal(\n options: RegistryRequestTerminalFormatOptions,\n): RegistryRequestTerminalOutput {\n return options.outputFormat === 'json' ? formatJson(options) : formatText(options);\n}\n\nexport function isRegistryRequestTerminalStatus(status: RegistryRequestStatus): boolean {\n return TERMINAL_STATUSES.has(status);\n}\n\nexport function isRegistryRequestSuccessStatus(status: RegistryRequestStatus): boolean {\n return SUCCESS_STATUSES.has(status);\n}\n\nfunction formatJson(options: RegistryRequestTerminalFormatOptions): RegistryRequestTerminalOutput {\n const { detail, presentation, registryAlias, registryRefreshWarning } = options;\n const { request } = detail;\n const diagnosticFields = {\n ...(detail.checks ? { checks: detail.checks } : {}),\n ...(detail.terminalFailure ? { terminalFailure: detail.terminalFailure } : {}),\n };\n let body: Readonly<Record<string, unknown>>;\n\n if (presentation.kind === 'lifecycle') {\n body = {\n ok: isRegistryRequestSuccessStatus(request.status),\n operation: presentation.operation,\n registry: registryAlias,\n requestId: request.id,\n status: request.status,\n ...diagnosticFields,\n ...(registryRefreshWarning ? { registryRefreshWarning } : {}),\n };\n } else {\n body = {\n ok: isRegistryRequestSuccessStatus(request.status) || !isRegistryRequestTerminalStatus(request.status),\n registry: registryAlias,\n request,\n ...diagnosticFields,\n ...(registryRefreshWarning ? { registryRefreshWarning } : {}),\n };\n }\n\n return { stdout: `${JSON.stringify(body)}\\n` };\n}\n\nfunction formatText(options: RegistryRequestTerminalFormatOptions): RegistryRequestTerminalOutput {\n const { detail, presentation, registryRefreshWarning } = options;\n const lines = presentationLines(presentation, detail.request);\n appendDiagnosticLines(lines, detail);\n\n let stderr: string | undefined;\n if (registryRefreshWarning) {\n stderr =\n presentation.kind === 'publish'\n ? `Package publication succeeded, but the local registry refresh failed: ${registryRefreshWarning}\\n`\n : `The operation succeeded, but the local registry refresh failed: ${registryRefreshWarning}\\n`;\n }\n return { stdout: `${lines.join('\\n')}\\n`, ...(stderr ? { stderr } : {}) };\n}\n\nfunction presentationLines(presentation: RegistryRequestPresentation, request: RegistryRequestSummary): string[] {\n if (presentation.kind === 'publish') {\n const lines = [`Publishing request ${request.id}: ${request.status}.`];\n if (request.status === 'ready_superseded') {\n lines.push('The publication was accepted; a newer change to the same package is already projected.');\n }\n return lines;\n }\n if (presentation.kind === 'lifecycle') {\n return [\n `${presentation.operation} request ${request.id} for ${presentation.packageName}@${presentation.packageVersion}: ${request.status}.`,\n ];\n }\n\n const lines = [`Request: ${request.id}`, `Status: ${request.status}`];\n if (request.currentStep) lines.push(`Step: ${request.currentStep}`);\n if (request.payload?.packageName) {\n lines.push(\n `Package: ${request.payload.packageName}${request.payload.packageVersion ? `@${request.payload.packageVersion}` : ''}`,\n );\n }\n return lines;\n}\n\nfunction appendDiagnosticLines(lines: string[], detail: RegistryRequestDetail): void {\n const failedChecks = detail.checks?.filter((check) => check.conclusion === 'failure') ?? [];\n if (failedChecks.length > 0) {\n lines.push('Failed checks:');\n for (const check of failedChecks) {\n lines.push(` ${check.checkId}: ${check.reasonCode ?? 'failure'}`);\n }\n }\n\n if (detail.terminalFailure) {\n lines.push(`Operational failure: ${detail.terminalFailure.reasonCode}.`);\n const { attempts, maxAttempts } = detail.terminalFailure;\n if (attempts !== undefined && maxAttempts !== undefined) {\n lines.push(`Retry attempts: ${attempts} of ${maxAttempts}.`);\n } else if (attempts !== undefined) {\n lines.push(`Retry attempts: ${attempts}.`);\n } else if (maxAttempts !== undefined) {\n lines.push(`Retry limit: ${maxAttempts} ${maxAttempts === 1 ? 'attempt' : 'attempts'}.`);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RegistryRequestTerminalFormatter.js","names":[],"sources":["../../src/reporting/RegistryRequestTerminalFormatter.ts"],"sourcesContent":["import type { UAPKGLifecycleCommandName, UAPKGOutputFormat } from '#cli/cli/UAPKGCommandLine.ts';\nimport type {\n RegistryRequestDetail,\n RegistryRequestStatus,\n RegistryRequestSummary,\n} from '#cli/control-plane/ControlPlaneTypes.ts';\n\nconst TERMINAL_STATUSES: ReadonlySet<RegistryRequestStatus> = new Set([\n 'ready',\n 'ready_superseded',\n 'rejected',\n 'operationally_failed',\n]);\nconst SUCCESS_STATUSES: ReadonlySet<RegistryRequestStatus> = new Set(['ready', 'ready_superseded']);\n\nexport type RegistryRequestPresentation =\n | { readonly kind: 'publish' }\n | {\n readonly kind: 'lifecycle';\n readonly operation: UAPKGLifecycleCommandName;\n readonly packageName: string;\n readonly packageVersion: string;\n }\n | { readonly kind: 'status' };\n\nexport interface RegistryRequestTerminalFormatOptions {\n readonly detail: RegistryRequestDetail;\n readonly registryAlias: string;\n readonly outputFormat: UAPKGOutputFormat;\n readonly presentation: RegistryRequestPresentation;\n readonly registryRefreshWarning?: string;\n}\n\nexport interface RegistryRequestTerminalOutput {\n readonly stdout: string;\n readonly stderr?: string;\n}\n\n/**\n * Formats the caller-visible terminal story consistently for publish,\n * lifecycle, and standalone request-status commands. The additive check and\n * failure fields have already been bounded by\n * `ControlPlaneClient.getRegistryRequestDetail` before reaching this boundary.\n */\nexport function formatRegistryRequestTerminal(\n options: RegistryRequestTerminalFormatOptions,\n): RegistryRequestTerminalOutput {\n return options.outputFormat === 'json' ? formatJson(options) : formatText(options);\n}\n\nexport function isRegistryRequestTerminalStatus(status: RegistryRequestStatus): boolean {\n return TERMINAL_STATUSES.has(status);\n}\n\nexport function isRegistryRequestSuccessStatus(status: RegistryRequestStatus): boolean {\n return SUCCESS_STATUSES.has(status);\n}\n\nfunction formatJson(options: RegistryRequestTerminalFormatOptions): RegistryRequestTerminalOutput {\n const { detail, presentation, registryAlias, registryRefreshWarning } = options;\n const { request } = detail;\n const diagnosticFields = {\n ...(detail.checks ? { checks: detail.checks } : {}),\n ...(detail.terminalFailure ? { terminalFailure: detail.terminalFailure } : {}),\n };\n let body: Readonly<Record<string, unknown>>;\n\n if (presentation.kind === 'lifecycle') {\n body = {\n ok: isRegistryRequestSuccessStatus(request.status),\n operation: presentation.operation,\n registry: registryAlias,\n requestId: request.id,\n status: request.status,\n ...diagnosticFields,\n ...(registryRefreshWarning ? { registryRefreshWarning } : {}),\n };\n } else {\n body = {\n ok: isRegistryRequestSuccessStatus(request.status) || !isRegistryRequestTerminalStatus(request.status),\n registry: registryAlias,\n request,\n ...diagnosticFields,\n ...(registryRefreshWarning ? { registryRefreshWarning } : {}),\n };\n }\n\n return { stdout: `${JSON.stringify(body)}\\n` };\n}\n\nfunction formatText(options: RegistryRequestTerminalFormatOptions): RegistryRequestTerminalOutput {\n const { detail, presentation, registryRefreshWarning } = options;\n const lines = presentationLines(presentation, detail.request);\n appendDiagnosticLines(lines, detail);\n\n let stderr: string | undefined;\n if (registryRefreshWarning) {\n stderr =\n presentation.kind === 'publish'\n ? `Package publication succeeded, but the local registry refresh failed: ${registryRefreshWarning}\\n`\n : `The operation succeeded, but the local registry refresh failed: ${registryRefreshWarning}\\n`;\n }\n return { stdout: `${lines.join('\\n')}\\n`, ...(stderr ? { stderr } : {}) };\n}\n\nfunction presentationLines(presentation: RegistryRequestPresentation, request: RegistryRequestSummary): string[] {\n if (presentation.kind === 'publish') {\n const lines = [`Publishing request ${request.id}: ${request.status}.`];\n if (request.status === 'ready_superseded') {\n lines.push('The publication was accepted; the Git registry already includes a newer change to this package.');\n }\n return lines;\n }\n if (presentation.kind === 'lifecycle') {\n return [\n `${presentation.operation} request ${request.id} for ${presentation.packageName}@${presentation.packageVersion}: ${request.status}.`,\n ];\n }\n\n const lines = [`Request: ${request.id}`, `Status: ${request.status}`];\n if (request.currentStep) lines.push(`Step: ${request.currentStep}`);\n if (request.payload?.packageName) {\n lines.push(\n `Package: ${request.payload.packageName}${request.payload.packageVersion ? `@${request.payload.packageVersion}` : ''}`,\n );\n }\n return lines;\n}\n\nfunction appendDiagnosticLines(lines: string[], detail: RegistryRequestDetail): void {\n const failedChecks = detail.checks?.filter((check) => check.conclusion === 'failure') ?? [];\n if (failedChecks.length > 0) {\n lines.push('Failed checks:');\n for (const check of failedChecks) {\n lines.push(` ${check.checkId}: ${check.reasonCode ?? 'failure'}`);\n }\n }\n\n if (detail.terminalFailure) {\n lines.push(`Operational failure: ${detail.terminalFailure.reasonCode}.`);\n const { attempts, maxAttempts } = detail.terminalFailure;\n if (attempts !== undefined && maxAttempts !== undefined) {\n lines.push(`Retry attempts: ${attempts} of ${maxAttempts}.`);\n } else if (attempts !== undefined) {\n lines.push(`Retry attempts: ${attempts}.`);\n } else if (maxAttempts !== undefined) {\n lines.push(`Retry limit: ${maxAttempts} ${maxAttempts === 1 ? 'attempt' : 'attempts'}.`);\n }\n }\n}\n"],"mappings":";AAOA,MAAM,oCAAwD,IAAI,IAAI;CACpE;CACA;CACA;CACA;AACF,CAAC;AACD,MAAM,mCAAuD,IAAI,IAAI,CAAC,SAAS,kBAAkB,CAAC;;;;;;;AA+BlG,SAAgB,8BACd,SAC+B;CAC/B,OAAO,QAAQ,iBAAiB,SAAS,WAAW,OAAO,IAAI,WAAW,OAAO;AACnF;AAEA,SAAgB,gCAAgC,QAAwC;CACtF,OAAO,kBAAkB,IAAI,MAAM;AACrC;AAEA,SAAgB,+BAA+B,QAAwC;CACrF,OAAO,iBAAiB,IAAI,MAAM;AACpC;AAEA,SAAS,WAAW,SAA8E;CAChG,MAAM,EAAE,QAAQ,cAAc,eAAe,2BAA2B;CACxE,MAAM,EAAE,YAAY;CACpB,MAAM,mBAAmB;EACvB,GAAI,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;EACjD,GAAI,OAAO,kBAAkB,EAAE,iBAAiB,OAAO,gBAAgB,IAAI,CAAC;CAC9E;CACA,IAAI;CAEJ,IAAI,aAAa,SAAS,aACxB,OAAO;EACL,IAAI,+BAA+B,QAAQ,MAAM;EACjD,WAAW,aAAa;EACxB,UAAU;EACV,WAAW,QAAQ;EACnB,QAAQ,QAAQ;EAChB,GAAG;EACH,GAAI,yBAAyB,EAAE,uBAAuB,IAAI,CAAC;CAC7D;MAEA,OAAO;EACL,IAAI,+BAA+B,QAAQ,MAAM,KAAK,CAAC,gCAAgC,QAAQ,MAAM;EACrG,UAAU;EACV;EACA,GAAG;EACH,GAAI,yBAAyB,EAAE,uBAAuB,IAAI,CAAC;CAC7D;CAGF,OAAO,EAAE,QAAQ,GAAG,KAAK,UAAU,IAAI,EAAE,IAAI;AAC/C;AAEA,SAAS,WAAW,SAA8E;CAChG,MAAM,EAAE,QAAQ,cAAc,2BAA2B;CACzD,MAAM,QAAQ,kBAAkB,cAAc,OAAO,OAAO;CAC5D,sBAAsB,OAAO,MAAM;CAEnC,IAAI;CACJ,IAAI,wBACF,SACE,aAAa,SAAS,YAClB,yEAAyE,uBAAuB,MAChG,mEAAmE,uBAAuB;CAElG,OAAO;EAAE,QAAQ,GAAG,MAAM,KAAK,IAAI,EAAE;EAAK,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;CAAG;AAC1E;AAEA,SAAS,kBAAkB,cAA2C,SAA2C;CAC/G,IAAI,aAAa,SAAS,WAAW;EACnC,MAAM,QAAQ,CAAC,sBAAsB,QAAQ,GAAG,IAAI,QAAQ,OAAO,EAAE;EACrE,IAAI,QAAQ,WAAW,oBACrB,MAAM,KAAK,iGAAiG;EAE9G,OAAO;CACT;CACA,IAAI,aAAa,SAAS,aACxB,OAAO,CACL,GAAG,aAAa,UAAU,WAAW,QAAQ,GAAG,OAAO,aAAa,YAAY,GAAG,aAAa,eAAe,IAAI,QAAQ,OAAO,EACpI;CAGF,MAAM,QAAQ,CAAC,YAAY,QAAQ,MAAM,WAAW,QAAQ,QAAQ;CACpE,IAAI,QAAQ,aAAa,MAAM,KAAK,SAAS,QAAQ,aAAa;CAClE,IAAI,QAAQ,SAAS,aACnB,MAAM,KACJ,YAAY,QAAQ,QAAQ,cAAc,QAAQ,QAAQ,iBAAiB,IAAI,QAAQ,QAAQ,mBAAmB,IACpH;CAEF,OAAO;AACT;AAEA,SAAS,sBAAsB,OAAiB,QAAqC;CACnF,MAAM,eAAe,OAAO,QAAQ,QAAQ,UAAU,MAAM,eAAe,SAAS,KAAK,CAAC;CAC1F,IAAI,aAAa,SAAS,GAAG;EAC3B,MAAM,KAAK,gBAAgB;EAC3B,KAAK,MAAM,SAAS,cAClB,MAAM,KAAK,KAAK,MAAM,QAAQ,IAAI,MAAM,cAAc,WAAW;CAErE;CAEA,IAAI,OAAO,iBAAiB;EAC1B,MAAM,KAAK,wBAAwB,OAAO,gBAAgB,WAAW,EAAE;EACvE,MAAM,EAAE,UAAU,gBAAgB,OAAO;EACzC,IAAI,aAAa,UAAa,gBAAgB,QAC5C,MAAM,KAAK,mBAAmB,SAAS,MAAM,YAAY,EAAE;OACtD,IAAI,aAAa,QACtB,MAAM,KAAK,mBAAmB,SAAS,EAAE;OACpC,IAAI,gBAAgB,QACzB,MAAM,KAAK,gBAAgB,YAAY,GAAG,gBAAgB,IAAI,YAAY,WAAW,EAAE;CAE3F;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"TextDiagnosticRenderer.js","names":[],"sources":["../../src/reporting/TextDiagnosticRenderer.ts"],"sourcesContent":["import type { Diagnostic } from '@uapkg/diagnostics';\nimport { formatPublishRequestFailed } from '@uapkg/diagnostics-format';\nimport type { DiagnosticRenderer } from './DiagnosticRenderer.js';\nimport type { TextSink } from './TextSink.js';\n\n/**\n * Plain-text diagnostic renderer — used by tests and by any non-TTY path\n * that prefers a deterministic string output over Ink. Stable format:\n *\n * [<icon>] <message>\n * → <hint>\n *\n * Icons mirror the legacy reporter so existing log-parsers still match:\n * `x` (error), `!` (warning), `i` (info).\n */\nexport class TextDiagnosticRenderer implements DiagnosticRenderer {\n public constructor(\n private readonly stdout: TextSink,\n private readonly stderr: TextSink,\n ) {}\n\n public render(diagnostics: readonly Diagnostic[], stream: 'stdout' | 'stderr'): void {\n if (diagnostics.length === 0) return;\n const sink = stream === 'stderr' ? this.stderr : this.stdout;\n for (const diagnostic of diagnostics) {\n if (diagnostic.code === 'PUBLISH_REQUEST_FAILED') {\n for (const line of formatPublishRequestFailed(diagnostic).split('\\n')) sink.writeLine(line);\n continue;\n }\n const icon = diagnostic.level === 'error' ? 'x' : diagnostic.level === 'warning' ? '!' : 'i';\n sink.writeLine(`[${icon}] ${diagnostic.message}`);\n if (diagnostic.hint) sink.writeLine(` → ${diagnostic.hint}`);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,IAAa,yBAAb,MAAkE;CAE7C;CACA;CAFnB,AAAO,YACL,AAAiB,QACjB,AAAiB,QACjB;EAFiB;EACA;CAChB;CAEH,AAAO,OAAO,aAAoC,QAAmC;EACnF,IAAI,YAAY,WAAW,GAAG;EAC9B,MAAM,OAAO,WAAW,WAAW,KAAK,SAAS,KAAK;EACtD,KAAK,MAAM,cAAc,aAAa;GACpC,IAAI,WAAW,SAAS,0BAA0B;IAChD,KAAK,MAAM,QAAQ,2BAA2B,UAAU,CAAC,CAAC,MAAM,IAAI,GAAG,KAAK,UAAU,IAAI;IAC1F;GACF;GACA,MAAM,OAAO,WAAW,UAAU,UAAU,MAAM,WAAW,UAAU,YAAY,MAAM;GACzF,KAAK,UAAU,IAAI,KAAK,IAAI,WAAW,SAAS;GAChD,IAAI,WAAW,MAAM,KAAK,UAAU,OAAO,WAAW,MAAM;EAC9D;CACF;AACF"}
1
+ {"version":3,"file":"TextDiagnosticRenderer.js","names":[],"sources":["../../src/reporting/TextDiagnosticRenderer.ts"],"sourcesContent":["import type { Diagnostic } from '@uapkg/diagnostics';\nimport { formatPublishRequestFailed } from '@uapkg/diagnostics-format';\nimport type { DiagnosticRenderer } from '#cli/reporting/DiagnosticRenderer.ts';\nimport type { TextSink } from '#cli/reporting/TextSink.ts';\n\n/**\n * Plain-text diagnostic renderer — used by tests and by any non-TTY path\n * that prefers a deterministic string output over Ink. Stable format:\n *\n * [<icon>] <message>\n * → <hint>\n *\n * Icons mirror the legacy reporter so existing log-parsers still match:\n * `x` (error), `!` (warning), `i` (info).\n */\nexport class TextDiagnosticRenderer implements DiagnosticRenderer {\n public constructor(\n private readonly stdout: TextSink,\n private readonly stderr: TextSink,\n ) {}\n\n public render(diagnostics: readonly Diagnostic[], stream: 'stdout' | 'stderr'): void {\n if (diagnostics.length === 0) return;\n const sink = stream === 'stderr' ? this.stderr : this.stdout;\n for (const diagnostic of diagnostics) {\n if (diagnostic.code === 'PUBLISH_REQUEST_FAILED') {\n for (const line of formatPublishRequestFailed(diagnostic).split('\\n')) sink.writeLine(line);\n continue;\n }\n const icon = diagnostic.level === 'error' ? 'x' : diagnostic.level === 'warning' ? '!' : 'i';\n sink.writeLine(`[${icon}] ${diagnostic.message}`);\n if (diagnostic.hint) sink.writeLine(` → ${diagnostic.hint}`);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,IAAa,yBAAb,MAAkE;CAE7C;CACA;CAFnB,AAAO,YACL,AAAiB,QACjB,AAAiB,QACjB;EAFiB;EACA;CAChB;CAEH,AAAO,OAAO,aAAoC,QAAmC;EACnF,IAAI,YAAY,WAAW,GAAG;EAC9B,MAAM,OAAO,WAAW,WAAW,KAAK,SAAS,KAAK;EACtD,KAAK,MAAM,cAAc,aAAa;GACpC,IAAI,WAAW,SAAS,0BAA0B;IAChD,KAAK,MAAM,QAAQ,2BAA2B,UAAU,CAAC,CAAC,MAAM,IAAI,GAAG,KAAK,UAAU,IAAI;IAC1F;GACF;GACA,MAAM,OAAO,WAAW,UAAU,UAAU,MAAM,WAAW,UAAU,YAAY,MAAM;GACzF,KAAK,UAAU,IAAI,KAAK,IAAI,WAAW,SAAS;GAChD,IAAI,WAAW,MAAM,KAAK,UAAU,OAAO,WAAW,MAAM;EAC9D;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uapkg/cli",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },
@@ -39,24 +39,36 @@
39
39
  "react": "19.2.4",
40
40
  "yargs": "18.0.0",
41
41
  "zod": "4.3.6",
42
- "@uapkg/common": "^1.3.1",
43
- "@uapkg/common-schema": "^1.3.1",
44
- "@uapkg/config": "^1.3.1",
45
- "@uapkg/diagnostics": "^1.3.1",
46
- "@uapkg/diagnostics-format": "^1.3.1",
47
- "@uapkg/installer": "^1.3.1",
48
- "@uapkg/log": "^1.3.1",
49
- "@uapkg/pack": "^1.3.1",
50
- "@uapkg/package-claims": "^1.3.1",
51
- "@uapkg/package-manifest": "^1.3.1",
52
- "@uapkg/package-manifest-schema": "^1.3.1",
53
- "@uapkg/registry-core": "^1.3.1",
54
- "@uapkg/registry-schema": "^1.3.1"
42
+ "@uapkg/common": "^1.4.0",
43
+ "@uapkg/common-schema": "^1.4.0",
44
+ "@uapkg/config": "^1.4.0",
45
+ "@uapkg/diagnostics": "^1.4.0",
46
+ "@uapkg/diagnostics-format": "^1.4.0",
47
+ "@uapkg/installer": "^1.4.0",
48
+ "@uapkg/log": "^1.4.0",
49
+ "@uapkg/pack": "^1.4.0",
50
+ "@uapkg/package-claims": "^1.4.0",
51
+ "@uapkg/package-manifest": "^1.4.0",
52
+ "@uapkg/package-manifest-schema": "^1.4.0",
53
+ "@uapkg/registry-core": "^1.4.0",
54
+ "@uapkg/registry-schema": "^1.4.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/react": "19.2.14",
58
58
  "tar": "7.5.13"
59
59
  },
60
+ "imports": {
61
+ "#cli/*.ts": {
62
+ "uapkg-source": "./src/*.ts",
63
+ "types": "./dist/*.d.ts",
64
+ "default": "./dist/*.js"
65
+ },
66
+ "#cli/*.tsx": {
67
+ "uapkg-source": "./src/*.tsx",
68
+ "types": "./dist/*.d.ts",
69
+ "default": "./dist/*.js"
70
+ }
71
+ },
60
72
  "scripts": {
61
73
  "uapkg": "vp run -w --cache cli:build -- --development && vp exec node dist/cli.js",
62
74
  "build": "node build/runCliBuild.ts",