From 738577a2ca66ff8834ef25351477ba33ed22653f Mon Sep 17 00:00:00 2001 From: Jason Cassidy Date: Fri, 24 Oct 2025 14:37:11 +0100 Subject: [PATCH 1/6] feat:hooks --- content/guide/nativescript-hooks-guide.md | 391 ++++++++++++++++++++++ content/sidebar.ts | 4 + 2 files changed, 395 insertions(+) create mode 100644 content/guide/nativescript-hooks-guide.md diff --git a/content/guide/nativescript-hooks-guide.md b/content/guide/nativescript-hooks-guide.md new file mode 100644 index 00000000..6182dcac --- /dev/null +++ b/content/guide/nativescript-hooks-guide.md @@ -0,0 +1,391 @@ +--- +title: Hooks +description: A Guide to using CLI hooks. +contributors: + - jcassidyav +--- +## Overview + +NativeScript hooks are executable pieces of code or Node.js scripts that can be added by application or plugin developers to customize the execution of particular NativeScript commands. They provide the power to perform special activities by plugging into different parts of the build process of your application. + +Hooks are added to the `hooks/` folder of a project by plugins, or are specified in the `nativescript.config.ts` by an application. + +For example, when `ns prepare ...` is executed, all script files in the `hooks/before-prepare/` and `hooks/after-prepare/` folders are executed as well. + +Starting with NativeScript 9.0 CLI (`npm install -g nativescript`), you can create hooks as either CommonJS with extension `.js` or ECMAScript Modules (ESM) with extension `.mjs`. + +For versions of the NativeScript CLI < 9.0 hooks must be written in CommonJS with extension `.js`. + +## Hook Execution Types + +The NativeScript CLI supports two different ways of executing hooks: + +**1. In-Process Execution** +- Available only for JavaScript hooks +- Executes within the CLI process +- Provides access to NativeScript CLI services via dependency injection +- Determined by the presence of `module.exports` statement +- **Recommended approach** for writing hooks + +**2. Spawned Execution** +- Executed via Node.js's `child_process.spawn` function +- Run from the project's root directory +- Cannot access NativeScript CLI services +- If a hook returns a non-zero exit code, the NativeScript command will throw an exception + +## In-Process Hooks + +**Basic Module Definition** + +To write an in-process hook, use the following module definition: + +```javascript +module.exports = function() { + // Hook implementation +} +``` + +**Using hookArgs** + +The hook function can accept a special argument named `hookArgs`, which is an object containing all arguments passed to the hooked method. + +**Example:** + +If the NativeScript CLI has a method `prepareJSApp` defined as: + +```typescript +@hook("prepareJSApp") +public async prepareJSApp(projectData: IProjectData, platformData: IPlatformData) { } +``` + +Then `hookArgs` will have the following structure: + +```json +{ + "projectData": {...}, + "platformData": {...} +} +``` + +**Using hookArgs in your hook:** + +```javascript +module.exports = function(hookArgs) { + console.log(hookArgs.projectData); +} +``` + +**Dependency Injection** + +NativeScript CLI is built with Dependency Injection and executes in-process hooks in a way that allows you to use any registered service from the injector. + +***Approach 1: Direct Service Injection*** + +```javascript +module.exports = function($logger, $fs, $projectDataService, hookArgs) { + $logger.info("Executing hook"); + // Use $fs, $projectDataService, etc. +} +``` + +***Approach 2: Injector Resolution*** + +```javascript +module.exports = function($injector, hookArgs) { + const $logger = $injector.resolve("$logger"); + const $fs = $injector.resolve("$fs"); + + $logger.info("Executing hook"); +} +``` + +**Important Notes:** +- Injected dependencies are resolved by name +- If you inject a non-existent service (e.g., `$logger1`), the CLI won't execute the hook and will show a warning +- When using `$injector` directly, no warning is shown for incorrect service names, and an error will be thrown during execution + +### Async Code in Hooks + +NativeScript CLI supports asynchronous code in hooks. If executing async code, you must return a Promise: + +```javascript +var mkdirp = require('mkdirp'); + +module.exports = function($logger) { + return new Promise(function(resolve, reject) { + mkdirp('somedir', function(err) { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +} +``` + +## Spawned Hooks + +Spawned hooks are executed via Node's `child_process.spawn` from the project's root directory. All options are passed to the script using environment variables: + +| Environment Variable | Description | +|---------------------|-------------| +| `TNS-VERSION` | The version of the NativeScript CLI | +| `TNS-HOOK_FULL_PATH` | The full path to the executed hook | +| `TNS-COMMANDLINE` | The exact command-line arguments passed to NativeScript CLI (e.g., `tns run ios --emulator`) | + +If a spawned hook returns a non-zero exit code, NativeScript CLI will throw an error and abort the command's execution. + +## Hook Types and Behaviors + +**Before/After Hooks** + +Hooks can execute code before or after a specific action: + +```javascript +module.exports = function(hookArgs) { + if (hookArgs.prepareData.release) { + console.log("Before executing release build."); + } +} +``` + +**Replacement Hooks** + +Hooks can replace the original CLI function (use sparingly): + +```javascript +module.exports = function(hookArgs, $logger) { + return () => { + $logger.info("Replaced the original CLI function."); + } +} +``` + +**Note:** Replacement hooks should only be used in specific, rare cases. Before/after hooks are strongly recommended. + +## Adding Hooks to Plugins + +To add hooks to your plugin, follow these steps: + +**1. Install the Hook Module** + +```bash +npm install nativescript-hook --save +``` + +For NativeScript 7+, use: +```bash +npm install @nativescript/hook --save +``` + +**2. Create postinstall.js** + +Create `postinstall.js` at the root folder of your plugin: + +```javascript +var hook = require("nativescript-hook")(__dirname); +hook.postinstall(); + +// For NativeScript 7+: +// require('@nativescript/hook')(__dirname).postinstall(); +``` + +**3. Create preuninstall.js** + +Create `preuninstall.js` at the root folder of your plugin: + +```javascript +var hook = require("nativescript-hook")(__dirname); +hook.preuninstall(); + +// For NativeScript 7+: +// require('@nativescript/hook')(__dirname).preuninstall(); +``` + +**4. Update package.json Scripts** + +Add the postinstall and preuninstall scripts: + +```json +{ + "scripts": { + "postinstall": "node postinstall.js", + "preuninstall": "node preuninstall.js" + } +} +``` + +**5. Declare Hooks in package.json** + +Define your hooks under the `nativescript` property: + +```json +{ + "nativescript": { + "hooks": [ + { + "type": "before-prepare", + "script": "lib/before-prepare.js" + }, + { + "type": "after-prepare", + "script": "lib/after-prepare.js", + "inject": true + } + ] + } +} +``` + +### Hook Configuration Properties + +#### type (Required) +Specifies when the hook should execute. Format: `before-` or `after-` + +#### script (Required) +The relative path from the plugin root to the hook implementation file. + +#### inject (Optional) +Boolean property indicating whether the hook should be executed in-process (`true`) or spawned (`false`). When `inject: true`, the hook can access NativeScript CLI services. + +#### name (Optional) +Custom name for the hook. Defaults to the plugin package name. + +**Example with custom name:** + +```json +{ + "nativescript": { + "hooks": [ + { + "type": "after-prepare", + "script": "lib/after-prepare.js", + "name": "my-custom-hook" + } + ] + } +} +``` + +## Adding Hooks to Applications + +You can define project-persistent hooks in your `nativescript.config.ts` file: + +```typescript +import { NativeScriptConfig } from '@nativescript/core' + +export default { + id: 'org.nativescript.app', + appPath: 'app', + appResourcesPath: 'App_Resources', + hooks: [ + { + type: 'before-prepare', + script: './scripts/hooks/before-prepare.js' + }, + { + type: 'after-prepare', + script: './scripts/hooks/after-prepare.js' + } + ] +} as NativeScriptConfig +``` + +## Configuration Reference + +### Hooks Configuration in nativescript.config.ts + +```typescript +hooks: [ + { + type: 'before-' | 'after-', + script: './path/to/script.js', + }, +] +``` + +## Available Hook Types + +The following hook types are available (prefix with `before-` or `after-`): + +| Hook Name | Description | Execution Context | +|-----------|-------------|-------------------| +| `buildAndroidPlugin` | Builds aar file for Android plugin | Runs during `prepareNativeApp` | +| `buildAndroid` | Builds Android app | During Android build process | +| `buildIOS` | Builds iOS app | During iOS build process | +| `checkEnvironment` | Validates project environment | Runs during `ns doctor`, `ns clean`, and most build commands | +| `checkForChanges` | Detects changes during watch | NativeScript CLI checks application state to decide if rebuild/reinstall/restart is needed | +| `install` | Application installed to device/emulator | After app installation | +| `prepare` | Compiles webpack and prepares native app | Prepares the application in platforms folder | +| `prepareNativeApp` | Prepares the actual native app | Runs during `prepare`/`watch` hook | +| `resolveCommand` | Resolves command and arguments | Runs before all CLI commands | +| `watch` | Sets up watchers for live sync | During `prepare` hook for live development | +| `watchPatterns` | Sets up watch patterns | During `watch` hook | + +### Hook Execution Examples + +**Example: Release Build Check** + +```javascript +module.exports = function(hookArgs) { + if (hookArgs.prepareData.release) { + console.log("Executing release build"); + // Modify API keys, enable production features, etc. + } +} +``` + +**Example: Using NativeScript Services** + +```javascript +module.exports = function($logger, $fs, hookArgs) { + $logger.info("Starting custom hook"); + + const configPath = hookArgs.projectData.projectDir + '/config.json'; + if ($fs.exists(configPath)) { + const config = $fs.readJson(configPath); + $logger.info(`Loaded config: ${JSON.stringify(config)}`); + } +} +``` + +**Example: ESM Hook** +In a file named `.mjs` +```javascript +export default function (hookArgs) { + console.log( + "MJS executing release build.", + JSON.stringify(hookArgs.prepareData) + ); +} + +``` + +## Hooks CLI Command + +Starting with NativeScript 9.0 CLI (`npm install -g nativescript`), a new commmand `ns hooks` is available. + +As described above `postinstall` scripts are used by plugins to install hooks to the correct location for execution, this is not compatible with users + using `npm install --ignore-scripts` ( or other settings which prevent script execution ) given that plugin that requires hooks utilises post install events to install the hooks. + +The new `ns hooks` command resolves this by providing a mechanism to install plugin hooks after a `npm install`. + +**Commands** + +| Command | Description | +| ---------- | ------------ | +| `ns hooks` | Lists the hooks that are in the installed plugins ( also `ns hooks list` ). | +| `ns hooks install` | Installs the hooks. | +| `ns hooks lock` | Creates a `nativescript-lock.json` file, this is a list of hooks per plugin and a hash of the script for each hook. | +| `ns hooks verify` | Compares the hooks in the plugins with what is specified in the `nativescript-lock.json` file, failing if a hook is not listed or the hash is not the same. | + +**Example usage** + +* Modify/Create `.npmrc` in the project root adding `ignore-scripts=true` +* After `npm i` run `ns hooks install` + +For extra peace of mind: + +Run `ns hooks lock` and `ns hooks install` will fail if any of the hooks have changed. + diff --git a/content/sidebar.ts b/content/sidebar.ts index eec69433..6f7bc798 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -312,6 +312,10 @@ export default [ }, ], }, + { + text: 'Hooks', + link: '/guide/nativescript-hooks-guide', + }, { text: 'Platform Version Handling', link: '/guide/platform-version-handling', From 55b18e3a00c7ada4287e180cdc83779af7a87d5b Mon Sep 17 00:00:00 2001 From: Jason Cassidy Date: Fri, 24 Oct 2025 15:57:02 +0100 Subject: [PATCH 2/6] feat:hooks --- content/guide/nativescript-hooks-guide.md | 191 +++++++++++++--------- 1 file changed, 112 insertions(+), 79 deletions(-) diff --git a/content/guide/nativescript-hooks-guide.md b/content/guide/nativescript-hooks-guide.md index 6182dcac..536db26b 100644 --- a/content/guide/nativescript-hooks-guide.md +++ b/content/guide/nativescript-hooks-guide.md @@ -4,11 +4,12 @@ description: A Guide to using CLI hooks. contributors: - jcassidyav --- + ## Overview NativeScript hooks are executable pieces of code or Node.js scripts that can be added by application or plugin developers to customize the execution of particular NativeScript commands. They provide the power to perform special activities by plugging into different parts of the build process of your application. -Hooks are added to the `hooks/` folder of a project by plugins, or are specified in the `nativescript.config.ts` by an application. +Hooks are added to the `hooks/` folder of a project by plugins, or are specified in the `nativescript.config.ts` by an application. For example, when `ns prepare ...` is executed, all script files in the `hooks/before-prepare/` and `hooks/after-prepare/` folders are executed as well. @@ -21,6 +22,7 @@ For versions of the NativeScript CLI < 9.0 hooks must be written in CommonJS wit The NativeScript CLI supports two different ways of executing hooks: **1. In-Process Execution** + - Available only for JavaScript hooks - Executes within the CLI process - Provides access to NativeScript CLI services via dependency injection @@ -28,6 +30,7 @@ The NativeScript CLI supports two different ways of executing hooks: - **Recommended approach** for writing hooks **2. Spawned Execution** + - Executed via Node.js's `child_process.spawn` function - Run from the project's root directory - Cannot access NativeScript CLI services @@ -40,7 +43,7 @@ The NativeScript CLI supports two different ways of executing hooks: To write an in-process hook, use the following module definition: ```javascript -module.exports = function() { +module.exports = function () { // Hook implementation } ``` @@ -70,8 +73,8 @@ Then `hookArgs` will have the following structure: **Using hookArgs in your hook:** ```javascript -module.exports = function(hookArgs) { - console.log(hookArgs.projectData); +module.exports = function (hookArgs) { + console.log(hookArgs.projectData) } ``` @@ -79,27 +82,28 @@ module.exports = function(hookArgs) { NativeScript CLI is built with Dependency Injection and executes in-process hooks in a way that allows you to use any registered service from the injector. -***Approach 1: Direct Service Injection*** +**_Approach 1: Direct Service Injection_** ```javascript -module.exports = function($logger, $fs, $projectDataService, hookArgs) { - $logger.info("Executing hook"); +module.exports = function ($logger, $fs, $projectDataService, hookArgs) { + $logger.info('Executing hook') // Use $fs, $projectDataService, etc. } ``` -***Approach 2: Injector Resolution*** +**_Approach 2: Injector Resolution_** ```javascript -module.exports = function($injector, hookArgs) { - const $logger = $injector.resolve("$logger"); - const $fs = $injector.resolve("$fs"); - - $logger.info("Executing hook"); +module.exports = function ($injector, hookArgs) { + const $logger = $injector.resolve('$logger') + const $fs = $injector.resolve('$fs') + + $logger.info('Executing hook') } ``` **Important Notes:** + - Injected dependencies are resolved by name - If you inject a non-existent service (e.g., `$logger1`), the CLI won't execute the hook and will show a warning - When using `$injector` directly, no warning is shown for incorrect service names, and an error will be thrown during execution @@ -109,18 +113,18 @@ module.exports = function($injector, hookArgs) { NativeScript CLI supports asynchronous code in hooks. If executing async code, you must return a Promise: ```javascript -var mkdirp = require('mkdirp'); +var mkdirp = require('mkdirp') -module.exports = function($logger) { - return new Promise(function(resolve, reject) { - mkdirp('somedir', function(err) { +module.exports = function ($logger) { + return new Promise(function (resolve, reject) { + mkdirp('somedir', function (err) { if (err) { - reject(err); + reject(err) } else { - resolve(); + resolve() } - }); - }); + }) + }) } ``` @@ -128,11 +132,11 @@ module.exports = function($logger) { Spawned hooks are executed via Node's `child_process.spawn` from the project's root directory. All options are passed to the script using environment variables: -| Environment Variable | Description | -|---------------------|-------------| -| `TNS-VERSION` | The version of the NativeScript CLI | -| `TNS-HOOK_FULL_PATH` | The full path to the executed hook | -| `TNS-COMMANDLINE` | The exact command-line arguments passed to NativeScript CLI (e.g., `tns run ios --emulator`) | +| Environment Variable | Description | +| -------------------- | -------------------------------------------------------------------------------------------- | +| `TNS-VERSION` | The version of the NativeScript CLI | +| `TNS-HOOK_FULL_PATH` | The full path to the executed hook | +| `TNS-COMMANDLINE` | The exact command-line arguments passed to NativeScript CLI (e.g., `tns run ios --emulator`) | If a spawned hook returns a non-zero exit code, NativeScript CLI will throw an error and abort the command's execution. @@ -143,9 +147,9 @@ If a spawned hook returns a non-zero exit code, NativeScript CLI will throw an e Hooks can execute code before or after a specific action: ```javascript -module.exports = function(hookArgs) { +module.exports = function (hookArgs) { if (hookArgs.prepareData.release) { - console.log("Before executing release build."); + console.log('Before executing release build.') } } ``` @@ -155,9 +159,9 @@ module.exports = function(hookArgs) { Hooks can replace the original CLI function (use sparingly): ```javascript -module.exports = function(hookArgs, $logger) { +module.exports = function (hookArgs, $logger) { return () => { - $logger.info("Replaced the original CLI function."); + $logger.info('Replaced the original CLI function.') } } ``` @@ -175,6 +179,7 @@ npm install nativescript-hook --save ``` For NativeScript 7+, use: + ```bash npm install @nativescript/hook --save ``` @@ -184,8 +189,8 @@ npm install @nativescript/hook --save Create `postinstall.js` at the root folder of your plugin: ```javascript -var hook = require("nativescript-hook")(__dirname); -hook.postinstall(); +var hook = require('nativescript-hook')(__dirname) +hook.postinstall() // For NativeScript 7+: // require('@nativescript/hook')(__dirname).postinstall(); @@ -196,8 +201,8 @@ hook.postinstall(); Create `preuninstall.js` at the root folder of your plugin: ```javascript -var hook = require("nativescript-hook")(__dirname); -hook.preuninstall(); +var hook = require('nativescript-hook')(__dirname) +hook.preuninstall() // For NativeScript 7+: // require('@nativescript/hook')(__dirname).preuninstall(); @@ -241,15 +246,19 @@ Define your hooks under the `nativescript` property: ### Hook Configuration Properties #### type (Required) + Specifies when the hook should execute. Format: `before-` or `after-` #### script (Required) + The relative path from the plugin root to the hook implementation file. #### inject (Optional) + Boolean property indicating whether the hook should be executed in-process (`true`) or spawned (`false`). When `inject: true`, the hook can access NativeScript CLI services. #### name (Optional) + Custom name for the hook. Defaults to the plugin package name. **Example with custom name:** @@ -282,13 +291,13 @@ export default { hooks: [ { type: 'before-prepare', - script: './scripts/hooks/before-prepare.js' + script: './scripts/hooks/before-prepare.js', }, { type: 'after-prepare', - script: './scripts/hooks/after-prepare.js' - } - ] + script: './scripts/hooks/after-prepare.js', + }, + ], } as NativeScriptConfig ``` @@ -309,28 +318,28 @@ hooks: [ The following hook types are available (prefix with `before-` or `after-`): -| Hook Name | Description | Execution Context | -|-----------|-------------|-------------------| -| `buildAndroidPlugin` | Builds aar file for Android plugin | Runs during `prepareNativeApp` | -| `buildAndroid` | Builds Android app | During Android build process | -| `buildIOS` | Builds iOS app | During iOS build process | -| `checkEnvironment` | Validates project environment | Runs during `ns doctor`, `ns clean`, and most build commands | -| `checkForChanges` | Detects changes during watch | NativeScript CLI checks application state to decide if rebuild/reinstall/restart is needed | -| `install` | Application installed to device/emulator | After app installation | -| `prepare` | Compiles webpack and prepares native app | Prepares the application in platforms folder | -| `prepareNativeApp` | Prepares the actual native app | Runs during `prepare`/`watch` hook | -| `resolveCommand` | Resolves command and arguments | Runs before all CLI commands | -| `watch` | Sets up watchers for live sync | During `prepare` hook for live development | -| `watchPatterns` | Sets up watch patterns | During `watch` hook | +| Hook Name | Description | Execution Context | +| -------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------ | +| `buildAndroidPlugin` | Builds aar file for Android plugin | Runs during `prepareNativeApp` | +| `buildAndroid` | Builds Android app | During Android build process | +| `buildIOS` | Builds iOS app | During iOS build process | +| `checkEnvironment` | Validates project environment | Runs during `ns doctor`, `ns clean`, and most build commands | +| `checkForChanges` | Detects changes during watch | NativeScript CLI checks application state to decide if rebuild/reinstall/restart is needed | +| `install` | Application installed to device/emulator | After app installation | +| `prepare` | Compiles webpack and prepares native app | Prepares the application in platforms folder | +| `prepareNativeApp` | Prepares the actual native app | Runs during `prepare`/`watch` hook | +| `resolveCommand` | Resolves command and arguments | Runs before all CLI commands | +| `watch` | Sets up watchers for live sync | During `prepare` hook for live development | +| `watchPatterns` | Sets up watch patterns | During `watch` hook | ### Hook Execution Examples **Example: Release Build Check** ```javascript -module.exports = function(hookArgs) { +module.exports = function (hookArgs) { if (hookArgs.prepareData.release) { - console.log("Executing release build"); + console.log('Executing release build') // Modify API keys, enable production features, etc. } } @@ -339,53 +348,77 @@ module.exports = function(hookArgs) { **Example: Using NativeScript Services** ```javascript -module.exports = function($logger, $fs, hookArgs) { - $logger.info("Starting custom hook"); - - const configPath = hookArgs.projectData.projectDir + '/config.json'; +module.exports = function ($logger, $fs, hookArgs) { + $logger.info('Starting custom hook') + + const configPath = hookArgs.projectData.projectDir + '/config.json' if ($fs.exists(configPath)) { - const config = $fs.readJson(configPath); - $logger.info(`Loaded config: ${JSON.stringify(config)}`); + const config = $fs.readJson(configPath) + $logger.info(`Loaded config: ${JSON.stringify(config)}`) } } ``` **Example: ESM Hook** In a file named `.mjs` + ```javascript export default function (hookArgs) { console.log( - "MJS executing release build.", - JSON.stringify(hookArgs.prepareData) - ); + 'MJS executing release build.', + JSON.stringify(hookArgs.prepareData), + ) } - ``` ## Hooks CLI Command -Starting with NativeScript 9.0 CLI (`npm install -g nativescript`), a new commmand `ns hooks` is available. +As described above these hooks are installed automatically through npm postinstall scripts included in the plugin package. -As described above `postinstall` scripts are used by plugins to install hooks to the correct location for execution, this is not compatible with users - using `npm install --ignore-scripts` ( or other settings which prevent script execution ) given that plugin that requires hooks utilises post install events to install the hooks. +However, if you (or your CI environment) install dependencies with: + +``` +npm install --ignore-scripts +``` + +then those postinstall scripts don’t run, which means: + +- Plugin hooks aren’t copied to the correct location (`hooks/` folder). +- Builds may fail or certain plugin functionality won’t work. + +Starting with NativeScript 9.0 CLI (`npm install -g nativescript`), a new command was introduced: + +``` +ns hooks +``` + +This command installs all plugin hooks after dependencies have been installed. + +So if your environment blocks postinstall scripts, you can now safely do: + +``` +npm install --ignore-scripts +ns hooks +``` -The new `ns hooks` command resolves this by providing a mechanism to install plugin hooks after a `npm install`. +The new `ns hooks` command will install the hooks into the proper project locations (as postinstall would have done). -**Commands** +**Available Commands** -| Command | Description | -| ---------- | ------------ | -| `ns hooks` | Lists the hooks that are in the installed plugins ( also `ns hooks list` ). | -| `ns hooks install` | Installs the hooks. | -| `ns hooks lock` | Creates a `nativescript-lock.json` file, this is a list of hooks per plugin and a hash of the script for each hook. | -| `ns hooks verify` | Compares the hooks in the plugins with what is specified in the `nativescript-lock.json` file, failing if a hook is not listed or the hash is not the same. | +| Command | Description | +| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ns hooks` | Lists the hooks that are in the installed plugins ( also `ns hooks list` ). | +| `ns hooks install` | Installs the hooks. | +| `ns hooks lock` | Creates a `nativescript-lock.json` file, this is a list of hooks per plugin and a hash of the script for each hook. | +| `ns hooks verify` | Compares the hooks in the plugins with what is specified in the `nativescript-lock.json` file, failing if a hook is not listed or the hash is not the same. | -**Example usage** +**For extra peace of mind** -* Modify/Create `.npmrc` in the project root adding `ignore-scripts=true` -* After `npm i` run `ns hooks install` +Typically the contents of the hook scripts do not change in plugins from version to version, to prevent unexpecected changes to hooks you can utilize the `lock` command. -For extra peace of mind: +This will: -Run `ns hooks lock` and `ns hooks install` will fail if any of the hooks have changed. +- Create a `nativescript-lock.json` file containing details of the current plugin hooks. +- Ensure that any future `ns hooks install` or `ns hooks verify` invocations will fail if any new hooks are introduced by dependencies or if these hook scripts differ from what was previously installed. +i.e. run `ns hooks lock` and `ns hooks install` will fail if any of the hooks have changed since the last time `ns hooks lock` has been executed. From 3dd855ddb738ec0fb981b18b4cc3a25d3333dfaf Mon Sep 17 00:00:00 2001 From: Jason Cassidy Date: Fri, 24 Oct 2025 16:21:45 +0100 Subject: [PATCH 3/6] feat:hooks --- .../{nativescript-hooks-guide.md => hooks.md} | 25 +++---------------- content/sidebar.ts | 2 +- 2 files changed, 4 insertions(+), 23 deletions(-) rename content/guide/{nativescript-hooks-guide.md => hooks.md} (95%) diff --git a/content/guide/nativescript-hooks-guide.md b/content/guide/hooks.md similarity index 95% rename from content/guide/nativescript-hooks-guide.md rename to content/guide/hooks.md index 536db26b..296dc5a3 100644 --- a/content/guide/nativescript-hooks-guide.md +++ b/content/guide/hooks.md @@ -174,12 +174,6 @@ To add hooks to your plugin, follow these steps: **1. Install the Hook Module** -```bash -npm install nativescript-hook --save -``` - -For NativeScript 7+, use: - ```bash npm install @nativescript/hook --save ``` @@ -189,11 +183,7 @@ npm install @nativescript/hook --save Create `postinstall.js` at the root folder of your plugin: ```javascript -var hook = require('nativescript-hook')(__dirname) -hook.postinstall() - -// For NativeScript 7+: -// require('@nativescript/hook')(__dirname).postinstall(); +require('@nativescript/hook')(__dirname).postinstall(); ``` **3. Create preuninstall.js** @@ -201,11 +191,7 @@ hook.postinstall() Create `preuninstall.js` at the root folder of your plugin: ```javascript -var hook = require('nativescript-hook')(__dirname) -hook.preuninstall() - -// For NativeScript 7+: -// require('@nativescript/hook')(__dirname).preuninstall(); +require('@nativescript/hook')(__dirname).preuninstall(); ``` **4. Update package.json Scripts** @@ -235,8 +221,7 @@ Define your hooks under the `nativescript` property: }, { "type": "after-prepare", - "script": "lib/after-prepare.js", - "inject": true + "script": "lib/after-prepare.js" } ] } @@ -253,10 +238,6 @@ Specifies when the hook should execute. Format: `before-` or `after- Date: Fri, 24 Oct 2025 16:25:46 +0100 Subject: [PATCH 4/6] feat:hooks --- content/guide/hooks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/guide/hooks.md b/content/guide/hooks.md index 296dc5a3..b2304f01 100644 --- a/content/guide/hooks.md +++ b/content/guide/hooks.md @@ -183,7 +183,7 @@ npm install @nativescript/hook --save Create `postinstall.js` at the root folder of your plugin: ```javascript -require('@nativescript/hook')(__dirname).postinstall(); +require('@nativescript/hook')(__dirname).postinstall() ``` **3. Create preuninstall.js** @@ -191,7 +191,7 @@ require('@nativescript/hook')(__dirname).postinstall(); Create `preuninstall.js` at the root folder of your plugin: ```javascript -require('@nativescript/hook')(__dirname).preuninstall(); +require('@nativescript/hook')(__dirname).preuninstall() ``` **4. Update package.json Scripts** From 9aa4e475b737b395bbfefe4039cd9962816f6552 Mon Sep 17 00:00:00 2001 From: Jason Cassidy Date: Sat, 25 Oct 2025 16:43:09 +0100 Subject: [PATCH 5/6] fix:add CommonJS and ESM examples --- content/guide/hooks.md | 183 +++++++++++++++++++++++++++++++---------- 1 file changed, 141 insertions(+), 42 deletions(-) diff --git a/content/guide/hooks.md b/content/guide/hooks.md index b2304f01..0a733bae 100644 --- a/content/guide/hooks.md +++ b/content/guide/hooks.md @@ -41,13 +41,21 @@ The NativeScript CLI supports two different ways of executing hooks: **Basic Module Definition** To write an in-process hook, use the following module definition: +::: code-group -```javascript +```javascript [thehook.mjs (ESM)] +export default function (hookArgs) { + // Hook implementation ESM +} +``` + +```javascript [thehook.js (CommonJS)] module.exports = function () { - // Hook implementation + // Hook implementation CommonJs } ``` +::: **Using hookArgs** The hook function can accept a special argument named `hookArgs`, which is an object containing all arguments passed to the hooked method. @@ -71,29 +79,58 @@ Then `hookArgs` will have the following structure: ``` **Using hookArgs in your hook:** +::: code-group -```javascript +```javascript [thehook.mjs (ESM)] +export default function (hookArgs) { + console.log(JSON.stringify(hookArgs.prepareData)) +} +``` + +```javascript [thehook.js (CommonJS)] module.exports = function (hookArgs) { console.log(hookArgs.projectData) } ``` +::: **Dependency Injection** NativeScript CLI is built with Dependency Injection and executes in-process hooks in a way that allows you to use any registered service from the injector. **_Approach 1: Direct Service Injection_** -```javascript +::: code-group + +```javascript [thehook.mjs (ESM)] +export default function ($logger, $fs, $projectDataService, hookArgs) { + $logger.info('Executing hook') + // Use $fs, $projectDataService, etc. +} +``` + +```javascript [thehook.js (CommonJS)] module.exports = function ($logger, $fs, $projectDataService, hookArgs) { $logger.info('Executing hook') // Use $fs, $projectDataService, etc. } ``` +::: **_Approach 2: Injector Resolution_** -```javascript +::: code-group + +```javascript [thehook.mjs (ESM)] +export default function ($injector, hookArgs) { + const $logger = $injector.resolve('$logger') + const $fs = $injector.resolve('$fs') + + $logger.info('Executing hook') +} +``` + +```javascript [thehook.js (CommonJS)] module.exports = function ($injector, hookArgs) { const $logger = $injector.resolve('$logger') const $fs = $injector.resolve('$fs') @@ -102,6 +139,8 @@ module.exports = function ($injector, hookArgs) { } ``` +::: + **Important Notes:** - Injected dependencies are resolved by name @@ -112,7 +151,25 @@ module.exports = function ($injector, hookArgs) { NativeScript CLI supports asynchronous code in hooks. If executing async code, you must return a Promise: -```javascript +::: code-group + +```javascript [thehook.mjs (ESM)] +import { mkdirp } from 'mkdirp' + +export default function ($logger) { + return new Promise(function (resolve, reject) { + mkdirp('somedir', function (err) { + if (err) { + reject(err) + } else { + resolve() + } + }) + }) +} +``` + +```javascript [thehook.js (CommonJS)] var mkdirp = require('mkdirp') module.exports = function ($logger) { @@ -128,6 +185,8 @@ module.exports = function ($logger) { } ``` +::: + ## Spawned Hooks Spawned hooks are executed via Node's `child_process.spawn` from the project's root directory. All options are passed to the script using environment variables: @@ -146,27 +205,25 @@ If a spawned hook returns a non-zero exit code, NativeScript CLI will throw an e Hooks can execute code before or after a specific action: -```javascript -module.exports = function (hookArgs) { +::: code-group + +```javascript [thehook.mjs (ESM)] +export default function (hookArgs) { if (hookArgs.prepareData.release) { console.log('Before executing release build.') } } ``` -**Replacement Hooks** - -Hooks can replace the original CLI function (use sparingly): - -```javascript -module.exports = function (hookArgs, $logger) { - return () => { - $logger.info('Replaced the original CLI function.') +```javascript [thehook.js (CommonJS)] +module.exports = function (hookArgs) { + if (hookArgs.prepareData.release) { + console.log('Before executing release build.') } } ``` -**Note:** Replacement hooks should only be used in specific, rare cases. Before/after hooks are strongly recommended. +::: ## Adding Hooks to Plugins @@ -180,20 +237,46 @@ npm install @nativescript/hook --save **2. Create postinstall.js** -Create `postinstall.js` at the root folder of your plugin: +Create a postinstall script at the root folder of your plugin: +::: code-group -```javascript +```javascript [postinstall.mjs (ESM)] +import path from 'path' +import hook from '@nativescript/hook' +import { fileURLToPath } from 'url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +hook(path.join(__dirname, '..')).postinstall() +``` + +```javascript [postinstall.js(CommonJS)] require('@nativescript/hook')(__dirname).postinstall() ``` +::: + **3. Create preuninstall.js** -Create `preuninstall.js` at the root folder of your plugin: +Create preuninstall script at the root folder of your plugin: + +::: code-group -```javascript -require('@nativescript/hook')(__dirname).preuninstall() +```javascript [postinstall.mjs (ESM)] +import path from 'path' +import hook from '@nativescript/hook' +import { fileURLToPath } from 'url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +hook(path.join(__dirname, '..')).preuninstall() ``` +```javascript [postinstall.js(CommonJS)] +require('@nativescript/hook')(__dirname).postinstall() +``` + +::: **4. Update package.json Scripts** Add the postinstall and preuninstall scripts: @@ -217,11 +300,11 @@ Define your hooks under the `nativescript` property: "hooks": [ { "type": "before-prepare", - "script": "lib/before-prepare.js" + "script": "lib/before-prepare.mjs" }, { "type": "after-prepare", - "script": "lib/after-prepare.js" + "script": "lib/after-prepare.mjs" } ] } @@ -250,7 +333,7 @@ Custom name for the hook. Defaults to the plugin package name. "hooks": [ { "type": "after-prepare", - "script": "lib/after-prepare.js", + "script": "lib/after-prepare.mjs", "name": "my-custom-hook" } ] @@ -272,11 +355,11 @@ export default { hooks: [ { type: 'before-prepare', - script: './scripts/hooks/before-prepare.js', + script: './scripts/hooks/before-prepare.mjs', }, { type: 'after-prepare', - script: './scripts/hooks/after-prepare.js', + script: './scripts/hooks/after-prepare.mjs', }, ], } as NativeScriptConfig @@ -290,7 +373,7 @@ export default { hooks: [ { type: 'before-' | 'after-', - script: './path/to/script.js', + script: './path/to/script.mjs', }, ] ``` @@ -317,7 +400,18 @@ The following hook types are available (prefix with `before-` or `after-`): **Example: Release Build Check** -```javascript +::: code-group + +```javascript [thehook.mjs (ESM)] +export default function (hookArgs) { + if (hookArgs.prepareData.release) { + console.log('Executing release build') + // Modify API keys, enable production features, etc. + } +} +``` + +```javascript [thehook.js (CommonJS)] module.exports = function (hookArgs) { if (hookArgs.prepareData.release) { console.log('Executing release build') @@ -326,10 +420,13 @@ module.exports = function (hookArgs) { } ``` +::: **Example: Using NativeScript Services** -```javascript -module.exports = function ($logger, $fs, hookArgs) { +::: code-group + +```javascript [thehook.mjs (ESM)] +export default function ($logger, $fs, hookArgs) { $logger.info('Starting custom hook') const configPath = hookArgs.projectData.projectDir + '/config.json' @@ -340,25 +437,27 @@ module.exports = function ($logger, $fs, hookArgs) { } ``` -**Example: ESM Hook** -In a file named `.mjs` +```javascript [thehook.js (CommonJS)] +module.exports = function ($logger, $fs, hookArgs) { + $logger.info('Starting custom hook') -```javascript -export default function (hookArgs) { - console.log( - 'MJS executing release build.', - JSON.stringify(hookArgs.prepareData), - ) + const configPath = hookArgs.projectData.projectDir + '/config.json' + if ($fs.exists(configPath)) { + const config = $fs.readJson(configPath) + $logger.info(`Loaded config: ${JSON.stringify(config)}`) + } } ``` +::: + ## Hooks CLI Command As described above these hooks are installed automatically through npm postinstall scripts included in the plugin package. However, if you (or your CI environment) install dependencies with: -``` +```bash npm install --ignore-scripts ``` @@ -369,7 +468,7 @@ then those postinstall scripts don’t run, which means: Starting with NativeScript 9.0 CLI (`npm install -g nativescript`), a new command was introduced: -``` +```bash ns hooks ``` @@ -377,7 +476,7 @@ This command installs all plugin hooks after dependencies have been installed. So if your environment blocks postinstall scripts, you can now safely do: -``` +```bash npm install --ignore-scripts ns hooks ``` From 522741d8fdcda1272ac806a2bcab1dcfcc9d41cd Mon Sep 17 00:00:00 2001 From: Jason Cassidy Date: Sat, 25 Oct 2025 16:47:31 +0100 Subject: [PATCH 6/6] fix: .js->.mjs in install scripts --- content/guide/hooks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/guide/hooks.md b/content/guide/hooks.md index 0a733bae..bb0589aa 100644 --- a/content/guide/hooks.md +++ b/content/guide/hooks.md @@ -284,8 +284,8 @@ Add the postinstall and preuninstall scripts: ```json { "scripts": { - "postinstall": "node postinstall.js", - "preuninstall": "node preuninstall.js" + "postinstall": "node postinstall.mjs", + "preuninstall": "node preuninstall.mjs" } } ```