feat: expose urlPath in deploy_component operation and CLI - #1113
Merged
Conversation
Components registered via package can now set urlPath at deploy time. The value is persisted in the root config alongside package/install, where the OptionsWatcher already reads and scopes it per component. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rejects urlPath containing '..' at the API boundary (prevents invalid config being written to disk before downstream validation fires) - Rejects empty urlPath strings - Requires package when urlPath is present (urlPath is only persisted in the package-based config path; payload deployments set it in their own harper-config.yaml) - Adds deployComponentValidator unit tests covering accept, reject, and path-traversal cases Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Avoids false-positive if validateBySchema returns undefined (success). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Contributor
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
Contributor
|
Reviewed; no blockers found. |
kriszyp
marked this pull request as ready for review
June 4, 2026 01:41
Contributor
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
cb1kenobi
approved these changes
Jun 4, 2026
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This was referenced Jul 23, 2026
kriszyp
added a commit
that referenced
this pull request
Jul 31, 2026
Where an application is served is a deployment concern, not an application concern, but `host`/`urlPath` were only readable from the config file that declared a plugin. For an application that is its own `config.yaml`, so the hostname and mount point had to be checked into the app — unoverridable from outside it (the env-config overlay is root-config-only). Worse, `host`/`urlPath` on a root-config *application* entry were silently inert. An application's plugin scopes read the application's own config.yaml and nothing carried the root entry's routing down to them, so `deploy_component urlPath=/api` (#1113) persisted a value that changed nothing. The flow described in #1113 only ever held for a root-declared *plugin*, whose scope does read the root config. The root config is now authoritative for where an application is served: my-app: host: api.example.com urlPath: /v1 - `scopeMount.ts` — pure mount model. `host` is replaced outright (an operator remapping a hostname must win over a value the app shipped). `urlPath` is composed rather than replaced, because a plugin's `urlPath` doubles as its app-internal base path (static's asset root, fastify's route prefix); replacing it would silently relocate app-internal URLs and collapse distinct plugins onto one path. Mount `/v1` + `static: { urlPath: assets }` → `/v1/assets/`. The composed value is a fixed point of `resolveBaseURLPath`, so downstream consumers keep resolving it without compounding the prefix. - Overlaid in `OptionsWatcher`, not at each call site, so `scope.options.getAll()` is the one effective view of a plugin's config. static's redirects and external paths, the EntryHandler's entry URLs, and fastify's route prefix are all correct with no changes of their own. Composed from the freshly-parsed file on every read, so live reload cannot compound the prefix. - Applied on both load paths: the root-config `package` recursion and the components-root directory scan. The scan is the path that matters most — it loads apps with no root entry at all, so a mount works for a payload-deployed app, not just an installed one. - `deploy_component` accepts and persists `host` alongside `urlPath`, rejecting a host that carries a port or path (it would never match the router's host compare). Also fixes the Scope `server` proxy passing a raw config `urlPath` straight to the router: a plugin that spreads its whole config section into these options (REST does) handed the router the literal './', which normalized to the unmatchable route '/.'. The proxy now resolves whichever source supplied the value. Documented in HarperFast/documentation#595.
kriszyp
added a commit
that referenced
this pull request
Jul 31, 2026
Where an application is served is a deployment concern, not an application concern, but `host`/`urlPath` were only readable from the config file that declared a plugin. For an application that is its own `config.yaml`, so the hostname and mount point had to be checked into the app — unoverridable from outside it (the env-config overlay is root-config-only). Worse, `host`/`urlPath` on a root-config *application* entry were silently inert. An application's plugin scopes read the application's own config.yaml and nothing carried the root entry's routing down to them, so `deploy_component urlPath=/api` (#1113) persisted a value that changed nothing. The flow described in #1113 only ever held for a root-declared *plugin*, whose scope does read the root config. The root config is now authoritative for where an application is served: my-app: host: api.example.com urlPath: /v1 - `scopeMount.ts` — pure mount model. `host` is replaced outright (an operator remapping a hostname must win over a value the app shipped). `urlPath` is composed rather than replaced, because a plugin's `urlPath` doubles as its app-internal base path (static's asset root, fastify's route prefix); replacing it would silently relocate app-internal URLs and collapse distinct plugins onto one path. Mount `/v1` + `static: { urlPath: assets }` → `/v1/assets/`. The composed value is a fixed point of `resolveBaseURLPath`, so downstream consumers keep resolving it without compounding the prefix. - Overlaid in `OptionsWatcher`, not at each call site, so `scope.options.getAll()` is the one effective view of a plugin's config. static's redirects and external paths, the EntryHandler's entry URLs, and fastify's route prefix are all correct with no changes of their own. Composed from the freshly-parsed file on every read, so live reload cannot compound the prefix. - Applied on both load paths: the root-config `package` recursion and the components-root directory scan. The scan is the path that matters most — it loads apps with no root entry at all, so a mount works for a payload-deployed app, not just an installed one. - `deploy_component` accepts and persists `host` alongside `urlPath`, rejecting a host that carries a port or path (it would never match the router's host compare). Also fixes the Scope `server` proxy passing a raw config `urlPath` straight to the router: a plugin that spreads its whole config section into these options (REST does) handed the router the literal './', which normalized to the unmatchable route '/.'. The proxy now resolves whichever source supplied the value. Documented in HarperFast/documentation#595.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Exposes the
urlPathcomponent config option (added in #397) as a first-class parameter of thedeploy_componentoperation and CLI.urlPathis accepted by the operation validator and persisted inharperdb-config.yamlalongsidepackagefor package-based component deploymentskey=valueargument parser:harper deploy project=my-app package=@my/pkg urlPath=/apiurlPathis rejected withoutpackage(it's only meaningful in the package-based config path; payload-deployed components configureurlPathin their ownharper-config.yaml)urlPathcontaining..or empty strings are rejected at the API boundary before reaching diskHow it flows
For package-based components the root
harperdb-config.yamlentry (e.g.my-app: { package: "...", urlPath: "/api" }) is whatOptionsWatcherreads and scopes on. That value then flows intoscope.options.getAll().urlPathand into the per-route middleware chain from #397.Known limitation (pre-existing, not introduced here)
addConfigdoes a fullsetInreplace on the component's config block. Re-deploying without specifyingurlPathwill overwrite the existing value — the same behaviour applies today forinstall_commandand other persisted fields.Tests
Added
deployComponentValidatorunit tests covering the accept, reject-without-package, path-traversal, empty-string, and missing-project cases.Generated by Claude Sonnet 4.6 🤖