Skip to content

feat(hooks): defineHook — typed hook authoring with ctx payload/wrap/abort - #6100

Draft
edusperoni wants to merge 2 commits into
feat/di-modernization-phase1from
feat/define-hook
Draft

feat(hooks): defineHook — typed hook authoring with ctx payload/wrap/abort#6100
edusperoni wants to merge 2 commits into
feat/di-modernization-phase1from
feat/define-hook

Conversation

@edusperoni

Copy link
Copy Markdown
Collaborator

Stacked on #6099 (feat/di-modernization-phase1).

PR Checklist

What is the current behavior?

Hooks are plain functions whose shape the CLI infers at runtime: services arrive via parameter-name injection (deprecated, runtime-traced), the payload via a magic hookArgs parameter, middleware by returning a function (implicit and undocumented), and aborting by throwing an error carrying stopExecution/errorAsWarning fields.

What is the new behavior?

A typed, explicit hook-authoring API — fully additive; every existing hook keeps working unchanged.

const { defineHook, inject, DoctorService } = require("nativescript/contracts");

module.exports = defineHook("before-prepare", async (ctx) => {
	const doctorService = inject(DoctorService);   // services via inject(), as everywhere else
	ctx.payload.args.push("--offline");            // the operation payload, mutable as before
	ctx.wrap(async (args, next) => next(...args)); // explicit middleware (was: return a function)
	ctx.abort("reason", { asWarning: true });      // explicit abort (was: throw + stopExecution)
});
  • lib/common/define-hook.ts is import-free (loading it can never boot a second CLI runtime) and re-exported from nativescript/contracts; definitions carry a Symbol.for marker so duplicated CLI copies in an extension tree recognize each other's definitions.
  • hooks-service runs definition modules on a dedicated path: no parameter-name parsing, no projectData promotion hack, no deprecation report. Plain-function hooks are untouched. .mjs default exports are recognized.
  • wrap() middlewares feed the exact channel legacy returned-functions use, so they compose with the @hook decorator chain identically (11 new tests pin payload identity/mutation, inject-in-handler, middleware wrap + short-circuit, abort both ways, legacy coexistence + tracer isolation, .mjs, and name-mismatch tracing).
  • One deliberate edge: per-directory hook results are now flattened once so multi-middleware hooks surface correctly. A legacy hook that returned an array of functions was silently dropped before and becomes middleware now — returning anything was always outside the documented contract (fulfillment values are ignored), so this is considered theoretical.
  • extending-cli.md now leads with defineHook; plain-function hooks and parameter-name injection remain documented as the transitional and legacy tiers.

Full suite: 110 files, 1609 passed / 38 skipped; the yok oracle, public-API test, and compat fixtures are untouched.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 287a7505-c08f-45cf-a659-2535538e6a9b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@edusperoni
edusperoni force-pushed the feat/define-hook branch 4 times, most recently from d75fed7 to 514c272 Compare July 30, 2026 02:25
Hook authors can now export a definition built with defineHook instead
of a plain function whose shape the CLI has to infer. The handler takes
a context object with the operation payload, an explicit wrap() for
middleware around the hooked method, and abort() for stopping the hook
as either a failure or a warning.

Definitions are marked with Symbol.for("nativescript:cli:hookDefinition")
so a duplicated CLI copy in an extension's dependency tree still
recognizes them. The definition path skips parameter-name resolution,
the projectData promotion hack and the deprecation report; plain
function hooks keep running through the existing path unchanged.

lib/common/define-hook.ts is import-free so a hook can load it without
booting a second runtime, and it is re-exported from
nativescript/contracts.
Yok extends Injector on the base branch, so definitions run in
runInInjectionContext(this.$injector, ...) directly and
inject(Injector) inside a hook returns the facade itself.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant