Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
114d316
feat: config + compiler option for rspack, vite with fallback to webpack
NathanWalker Mar 20, 2025
2451f15
feat: bundler config
NathanWalker Mar 21, 2025
0af7460
Merge remote-tracking branch 'origin/main' into feat/bundler-config
NathanWalker Mar 24, 2025
247f961
feat: bundler config refactor (#5840)
vallemar Mar 25, 2025
5b80974
test: bundler and bundlerConfigPath (#5841)
vallemar Mar 25, 2025
fd695d1
feat: wip vite setup
NathanWalker Apr 22, 2025
a369f81
feat(hooks): support esm
NathanWalker Jul 8, 2025
312db9e
chore: 9.0.0-alpha.0
NathanWalker Jul 8, 2025
cbfe8c2
Merge remote-tracking branch 'origin/main' into feat/hooks-esm-support
NathanWalker Jul 18, 2025
d094859
Merge remote-tracking branch 'origin' into pre/v9-combined-testing
NathanWalker Jul 18, 2025
8013cee
Merge branch 'feat/hooks-esm-support' into pre/v9-combined-testing
NathanWalker Jul 18, 2025
e502c0b
Merge remote-tracking branch 'origin/main' into feat/bundler-config
NathanWalker Jul 18, 2025
77e2516
Merge branch 'feat/bundler-config' into pre/v9-combined-testing
NathanWalker Jul 18, 2025
0cc056b
feat(vite): ensure cli flags are passed
NathanWalker Jul 21, 2025
4b9e451
feat(hooks): support esm
NathanWalker Jul 21, 2025
6b96879
Merge branch 'feat/hooks-esm-support' into pre/v9-combined-testing
NathanWalker Jul 21, 2025
b6f6bae
chore: 9.0.0-alpha.2
NathanWalker Jul 21, 2025
dba4e1a
chore: updated lock
NathanWalker Jul 27, 2025
bb9af69
Merge remote-tracking branch 'origin/main' into pre/v9-combined-testing
NathanWalker Jul 27, 2025
f3a3b90
chore: 9.0.0-alpha.3
NathanWalker Jul 27, 2025
b3bbc1a
feat(hooks): allow cjs extensions
NathanWalker Aug 3, 2025
89daf8a
chore: 9.0.0-alpha.4
NathanWalker Aug 3, 2025
287ebed
Merge remote-tracking branch 'origin/main' into pre/v9-combined-testing
NathanWalker Aug 7, 2025
8a12366
fix: prevent TypeError: Body is unusable during unit test dep generation
NathanWalker Aug 7, 2025
6de802d
chore: cleanup
NathanWalker Aug 7, 2025
15f17df
feat: vite support phase 1
NathanWalker Aug 9, 2025
d68a93d
chore: 9.0.0-alpha.5
NathanWalker Aug 9, 2025
513d669
fix(android): livesync tool connect race condition
NathanWalker Sep 4, 2025
82f19ce
Merge remote-tracking branch 'origin/main' into pre/v9-combined-testing
NathanWalker Sep 4, 2025
1d92c55
Merge remote-tracking branch 'origin/main' into pre/v9-combined-testing
NathanWalker Sep 4, 2025
0614eb7
chore: CodeQL Advanced workflow configuration
NathanWalker Sep 5, 2025
17c17e1
fix: clear all deprecated dependencies (#5858)
NathanWalker Sep 5, 2025
c97e80b
feat: add Dependabot configuration (#5859)
UlisesGascon Sep 8, 2025
1aeb086
feat: add Dependency Review Action workflow (#5860)
UlisesGascon Sep 8, 2025
7fd7b21
chore(deps): bump prettier from 3.5.2 to 3.6.2 (#5861)
dependabot[bot] Sep 10, 2025
242c219
chore(deps): bump semver and @types/semver (#5862)
dependabot[bot] Sep 10, 2025
3d7d41a
chore(deps-dev): bump chai and @types/chai (#5863)
dependabot[bot] Sep 10, 2025
07a9cdb
chore(deps): bump minimatch from 10.0.1 to 10.0.3 (#5864)
dependabot[bot] Sep 10, 2025
8bac0e4
chore(deps-dev): bump lint-staged from 15.4.3 to 15.5.2 (#5865)
dependabot[bot] Sep 10, 2025
e923424
chore(deps-dev): bump sinon from 19.0.2 to 19.0.5 (#5866)
dependabot[bot] Sep 10, 2025
dcd69c4
chore(deps-dev): bump chai-as-promised and @types/chai-as-promised (#…
dependabot[bot] Sep 10, 2025
69bd8b2
chore(deps): bump ws and @types/ws (#5868)
dependabot[bot] Sep 10, 2025
469cb7c
chore(deps): bump marked from 15.0.7 to 15.0.12 (#5869)
dependabot[bot] Sep 10, 2025
3f2018d
chore(deps): bump source-map from 0.7.4 to 0.7.6 (#5870)
dependabot[bot] Sep 10, 2025
bad5548
chore(deps-dev): bump chai and @types/chai in /packages/doctor (#5871)
dependabot[bot] Sep 10, 2025
f03bdbc
chore(deps-dev): bump mocha from 11.1.0 to 11.7.2 in /packages/doctor…
dependabot[bot] Sep 10, 2025
5d1d3e7
chore(deps-dev): bump @types/lodash from 4.17.15 to 4.17.20 in /packa…
dependabot[bot] Sep 10, 2025
bffa6b7
chore(deps-dev): bump typescript from 5.4.5 to 5.9.2 in /packages/doc…
dependabot[bot] Sep 10, 2025
265de70
chore(deps-dev): bump @types/semver from 7.5.8 to 7.7.1 in /packages/…
dependabot[bot] Sep 10, 2025
102ba27
chore(deps): bump envinfo and @types/envinfo in /packages/nativescrip…
dependabot[bot] Sep 10, 2025
69801f5
chore(deps): bump ossf/scorecard-action from 2.4.0 to 2.4.2 (#5877)
dependabot[bot] Sep 10, 2025
2babb59
chore(deps): bump actions/checkout from 2 to 5 (#5878)
dependabot[bot] Sep 10, 2025
c2105ef
chore(deps): bump actions/setup-node from 3 to 5 (#5879)
dependabot[bot] Sep 10, 2025
10d9f82
Enhance Workflows Security (#5880)
UlisesGascon Sep 10, 2025
de0d226
feat: vite copy file handling and incremental isolated copy for faste…
NathanWalker Sep 10, 2025
6238093
Merge remote-tracking branch 'origin/main' into pre/v9-combined-testing
NathanWalker Sep 16, 2025
e901ec3
fix: remove lock from default clean
NathanWalker Sep 16, 2025
b901f54
chore: 9.0.0-alpha.9
NathanWalker Sep 16, 2025
bb37020
Merge remote-tracking branch 'origin/main' into pre/v9-combined-testing
NathanWalker Sep 16, 2025
b883783
Merge remote-tracking branch 'origin/main' into pre/v9-combined-testing
NathanWalker Sep 16, 2025
ed168d4
chore: 9.0.0-alpha.10
NathanWalker Sep 16, 2025
54e2e9c
fix(vite): ensure proper mode
NathanWalker Sep 22, 2025
d941e23
chore: 9.0.0-alpha.11
NathanWalker Sep 22, 2025
4b0c935
fix: windows glob paths used in copy operations not working. (#5882)
jcassidyav Sep 26, 2025
7eba5f2
Merge remote-tracking branch 'origin/main' into pre/v9-combined-testing
NathanWalker Sep 26, 2025
89e383a
chore: 9.0.0-alpha.12
NathanWalker Sep 26, 2025
a2b4ee3
feat(vite): bundler handling
NathanWalker Oct 1, 2025
e3c0c10
chore: 9.0.0-alpha.13
NathanWalker Oct 1, 2025
08e98c2
feat: hooks command (#5884)
jcassidyav Oct 17, 2025
aad3ead
Merge remote-tracking branch 'origin/main' into pre/v9-combined-testing
NathanWalker Nov 8, 2025
5c9c25b
fix: use file-system apis instead of node fs directly in bundler
NathanWalker Nov 8, 2025
7519c06
chore: 9.0.0-alpha.14
NathanWalker Nov 8, 2025
a724418
chore: cleanup
NathanWalker Nov 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/man_pages/project/hooks/hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<% if (isJekyll) { %>---
title: ns hooks
position: 1
---<% } %>

# ns create

### Description

Manages lifecycle hooks from installed plugins.

### Commands

Usage | Synopsis
---------|---------
Install | `$ ns hooks install`
List | `$ ns hooks list`
Lock | `$ ns hooks lock`
Verify | `$ ns hooks verify`

#### Install

Installs hooks from each installed plugin dependency.

#### List

Lists the plugins which have hooks and which scripts they install

#### Lock

Generates a `hooks-lock.json` containing the hooks that are in the current versions of the plugins.

#### Verify

Verifies that the hooks contained in the installed plugins match those listed in the `hooks-lock.json` file.
1 change: 1 addition & 0 deletions docs/man_pages/start.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Command | Description
[plugin](lib-management/plugin.html) | Lets you manage the plugins for your project.
[open](project/configuration/open.md) | Opens the native project in Xcode/Android Studio.
[widget ios](project/configuration/widget.md) | Adds a new iOS widget to the project.
[hooks](project/hooks/hooks.html) | Installs lifecycle hooks from plugins.
## Publishing Commands
Command | Description
---|---
Expand Down
9 changes: 9 additions & 0 deletions lib/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,15 @@ injector.requireCommand("plugin|update", "./commands/plugin/update-plugin");
injector.requireCommand("plugin|build", "./commands/plugin/build-plugin");
injector.requireCommand("plugin|create", "./commands/plugin/create-plugin");

injector.requireCommand(
["hooks|*list", "hooks|install"],
"./commands/hooks/hooks",
);
injector.requireCommand(
["hooks|lock", "hooks|verify"],
"./commands/hooks/hooks-lock",
);

injector.require("doctorService", "./services/doctor-service");
injector.require("xcprojService", "./services/xcproj-service");
injector.require("versionsService", "./services/versions-service");
Expand Down
118 changes: 118 additions & 0 deletions lib/commands/hooks/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import * as _ from "lodash";
import { IProjectData } from "../../definitions/project";
import { IPluginData } from "../../definitions/plugins";
import { ICommandParameter } from "../../common/definitions/commands";
import { IErrors, IFileSystem } from "../../common/declarations";
import path = require("path");
import * as crypto from "crypto";

export const LOCK_FILE_NAME = "nativescript-lock.json";
export interface OutputHook {
type: string;
hash: string;
}

export interface OutputPlugin {
name: string;
hooks: OutputHook[];
}

export class HooksVerify {
public allowedParameters: ICommandParameter[] = [];

constructor(
protected $projectData: IProjectData,
protected $errors: IErrors,
protected $fs: IFileSystem,
protected $logger: ILogger,
) {
this.$projectData.initializeProjectData();
}

protected async verifyHooksLock(
plugins: IPluginData[],
hooksLockPath: string,
): Promise<void> {
let lockFileContent: string;
let hooksLock: OutputPlugin[];

try {
lockFileContent = this.$fs.readText(hooksLockPath, "utf8");
hooksLock = JSON.parse(lockFileContent);
} catch (err) {
this.$errors.fail(
`❌ Failed to read or parse ${LOCK_FILE_NAME} at ${hooksLockPath}`,
);
}

const lockMap = new Map<string, Map<string, string>>(); // pluginName -> hookType -> hash

for (const plugin of hooksLock) {
const hookMap = new Map<string, string>();
for (const hook of plugin.hooks) {
hookMap.set(hook.type, hook.hash);
}
lockMap.set(plugin.name, hookMap);
}

let isValid = true;

for (const plugin of plugins) {
const pluginLockHooks = lockMap.get(plugin.name);

if (!pluginLockHooks) {
this.$logger.error(
`❌ Plugin '${plugin.name}' not found in ${LOCK_FILE_NAME}`,
);
isValid = false;
continue;
}

for (const hook of plugin.nativescript?.hooks || []) {
const expectedHash = pluginLockHooks.get(hook.type);

if (!expectedHash) {
this.$logger.error(
`❌ Missing hook '${hook.type}' for plugin '${plugin.name}' in ${LOCK_FILE_NAME}`,
);
isValid = false;
continue;
}

let fileContent: string | Buffer<ArrayBufferLike>;

try {
fileContent = this.$fs.readFile(
path.join(plugin.fullPath, hook.script),
);
} catch (err) {
this.$logger.error(
`❌ Cannot read script file '${hook.script}' for hook '${hook.type}' in plugin '${plugin.name}'`,
);
isValid = false;
continue;
}

const actualHash = crypto
.createHash("sha256")
.update(fileContent)
.digest("hex");

if (actualHash !== expectedHash) {
this.$logger.error(
`❌ Hash mismatch for '${hook.script}' (${hook.type} in ${plugin.name}):`,
);
this.$logger.error(` Expected: ${expectedHash}`);
this.$logger.error(` Actual: ${actualHash}`);
isValid = false;
}
}
}

if (isValid) {
this.$logger.info("✅ All hooks verified successfully. No issues found.");
} else {
this.$errors.fail("❌ One or more hooks failed verification.");
}
}
}
135 changes: 135 additions & 0 deletions lib/commands/hooks/hooks-lock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { IProjectData } from "../../definitions/project";
import { IPluginsService, IPluginData } from "../../definitions/plugins";
import { ICommand, ICommandParameter } from "../../common/definitions/commands";
import { IErrors, IFileSystem } from "../../common/declarations";
import { injector } from "../../common/yok";
import path = require("path");
import * as crypto from "crypto";
import {
HooksVerify,
LOCK_FILE_NAME,
OutputHook,
OutputPlugin,
} from "./common";

export class HooksLockPluginCommand implements ICommand {
public allowedParameters: ICommandParameter[] = [];

constructor(
private $pluginsService: IPluginsService,
private $projectData: IProjectData,
private $errors: IErrors,
private $fs: IFileSystem,
private $logger: ILogger,
) {
this.$projectData.initializeProjectData();
}

public async execute(): Promise<void> {
const plugins: IPluginData[] =
await this.$pluginsService.getAllInstalledPlugins(this.$projectData);
if (plugins && plugins.length > 0) {
const pluginsWithHooks: IPluginData[] = [];
for (const plugin of plugins) {
if (plugin.nativescript?.hooks?.length > 0) {
pluginsWithHooks.push(plugin);
}
}

await this.writeHooksLockFile(
pluginsWithHooks,
this.$projectData.projectDir,
);
} else {
this.$logger.info("No plugins with hooks found.");
}
}

public async canExecute(args: string[]): Promise<boolean> {
return true;
}

private async writeHooksLockFile(
plugins: IPluginData[],
outputDir: string,
): Promise<void> {
const output: OutputPlugin[] = [];

for (const plugin of plugins) {
const hooks: OutputHook[] = [];

for (const hook of plugin.nativescript?.hooks || []) {
try {
const fileContent = this.$fs.readFile(
path.join(plugin.fullPath, hook.script),
);
const hash = crypto
.createHash("sha256")
.update(fileContent)
.digest("hex");

hooks.push({
type: hook.type,
hash,
});
} catch (err) {
this.$logger.warn(
`Warning: Failed to read script '${hook.script}' for plugin '${plugin.name}'. Skipping this hook.`,
);
continue;
}
}

output.push({ name: plugin.name, hooks });
}

const filePath = path.resolve(outputDir, LOCK_FILE_NAME);

try {
this.$fs.writeFile(filePath, JSON.stringify(output, null, 2), "utf8");
this.$logger.info(`✅ ${LOCK_FILE_NAME} written to: ${filePath}`);
} catch (err) {
this.$errors.fail(`❌ Failed to write ${LOCK_FILE_NAME}: ${err}`);
}
}
}

export class HooksVerifyPluginCommand extends HooksVerify implements ICommand {
public allowedParameters: ICommandParameter[] = [];

constructor(
private $pluginsService: IPluginsService,
$projectData: IProjectData,
$errors: IErrors,
$fs: IFileSystem,
$logger: ILogger,
) {
super($projectData, $errors, $fs, $logger);
}

public async execute(): Promise<void> {
const plugins: IPluginData[] =
await this.$pluginsService.getAllInstalledPlugins(this.$projectData);
if (plugins && plugins.length > 0) {
const pluginsWithHooks: IPluginData[] = [];
for (const plugin of plugins) {
if (plugin.nativescript?.hooks?.length > 0) {
pluginsWithHooks.push(plugin);
}
}
await this.verifyHooksLock(
pluginsWithHooks,
path.join(this.$projectData.projectDir, LOCK_FILE_NAME),
);
} else {
this.$logger.info("No plugins with hooks found.");
}
}

public async canExecute(args: string[]): Promise<boolean> {
return true;
}
}

injector.registerCommand(["hooks|lock"], HooksLockPluginCommand);
injector.registerCommand(["hooks|verify"], HooksVerifyPluginCommand);
Loading