Skip to content

feat(hooks): support esm #5843

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
100 changes: 57 additions & 43 deletions lib/common/services/hooks-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import { color } from "../../color";
import { memoize } from "../decorators";

class Hook implements IHook {
constructor(public name: string, public fullPath: string) {}
constructor(
public name: string,
public fullPath: string,
) {}
}

export class HooksService implements IHooksService {
Expand All @@ -45,7 +48,7 @@ export class HooksService implements IHooksService {
private $projectHelper: IProjectHelper,
private $options: IOptions,
private $performanceService: IPerformanceService,
private $projectConfigService: IProjectConfigService
private $projectConfigService: IProjectConfigService,
) {}

public get hookArgsName(): string {
Expand All @@ -69,12 +72,12 @@ export class HooksService implements IHooksService {

if (projectDir) {
this.hooksDirectories.push(
path.join(projectDir, HooksService.HOOKS_DIRECTORY_NAME)
path.join(projectDir, HooksService.HOOKS_DIRECTORY_NAME),
);
}

this.$logger.trace(
"Hooks directories: " + util.inspect(this.hooksDirectories)
"Hooks directories: " + util.inspect(this.hooksDirectories),
);

const customHooks = this.$projectConfigService.getValue("hooks", []);
Expand All @@ -91,7 +94,7 @@ export class HooksService implements IHooksService {

public executeBeforeHooks(
commandName: string,
hookArguments?: IDictionary<any>
hookArguments?: IDictionary<any>,
): Promise<void> {
const beforeHookName = `before-${HooksService.formatHookName(commandName)}`;
const traceMessage = `BeforeHookName for command ${commandName} is ${beforeHookName}`;
Expand All @@ -100,7 +103,7 @@ export class HooksService implements IHooksService {

public executeAfterHooks(
commandName: string,
hookArguments?: IDictionary<any>
hookArguments?: IDictionary<any>,
): Promise<void> {
const afterHookName = `after-${HooksService.formatHookName(commandName)}`;
const traceMessage = `AfterHookName for command ${commandName} is ${afterHookName}`;
Expand All @@ -110,7 +113,7 @@ export class HooksService implements IHooksService {
private async executeHooks(
hookName: string,
traceMessage: string,
hookArguments?: IDictionary<any>
hookArguments?: IDictionary<any>,
): Promise<any> {
if (this.$config.DISABLE_HOOKS || !this.$options.hooks) {
return;
Expand All @@ -135,8 +138,8 @@ export class HooksService implements IHooksService {
await this.executeHooksInDirectory(
hooksDirectory,
hookName,
hookArguments
)
hookArguments,
),
);
}

Expand All @@ -148,8 +151,8 @@ export class HooksService implements IHooksService {
this.$projectHelper.projectDir,
hookName,
hook,
hookArguments
)
hookArguments,
),
);
}
} catch (err) {
Expand All @@ -160,11 +163,16 @@ export class HooksService implements IHooksService {
return _.flatten(results);
}

private isESModule(hook: IHook): boolean {
const ext = path.extname(hook.fullPath).toLowerCase();
return ext === ".mjs";
}

private async executeHook(
directoryPath: string,
hookName: string,
hook: IHook,
hookArguments?: IDictionary<any>
hookArguments?: IDictionary<any>,
): Promise<any> {
hookArguments = hookArguments || {};

Expand All @@ -173,15 +181,18 @@ export class HooksService implements IHooksService {
const relativePath = path.relative(directoryPath, hook.fullPath);
const trackId = relativePath.replace(
new RegExp("\\" + path.sep, "g"),
AnalyticsEventLabelDelimiter
AnalyticsEventLabelDelimiter,
);
const isESM = this.isESModule(hook);
let command = this.getSheBangInterpreter(hook);
let inProc = false;
if (!command) {
command = hook.fullPath;
if (path.extname(hook.fullPath).toLowerCase() === ".js") {
if ([".mjs", ".js"].includes(path.extname(hook.fullPath).toLowerCase())) {
command = process.argv[0];
inProc = this.shouldExecuteInProcess(this.$fs.readText(hook.fullPath));
inProc = isESM
? true
: this.shouldExecuteInProcess(this.$fs.readText(hook.fullPath));
}
}

Expand All @@ -190,24 +201,30 @@ export class HooksService implements IHooksService {
this.$logger.trace(
"Executing %s hook at location %s in-process",
hookName,
hook.fullPath
hook.fullPath,
);
const hookEntryPoint = require(hook.fullPath);
let hookEntryPoint;
if (isESM) {
const { default: hookFn } = await import(hook.fullPath);
hookEntryPoint = hookFn;
} else {
hookEntryPoint = require(hook.fullPath);
}

this.$logger.trace(`Validating ${hookName} arguments.`);

const invalidArguments = this.validateHookArguments(
hookEntryPoint,
hook.fullPath
hook.fullPath,
);

if (invalidArguments.length) {
this.$logger.warn(
`${
hook.fullPath
} will NOT be executed because it has invalid arguments - ${color.grey(
invalidArguments.join(", ")
)}.`
invalidArguments.join(", "),
)}.`,
);
return;
}
Expand All @@ -220,14 +237,13 @@ export class HooksService implements IHooksService {
const projectDataHookArg =
hookArguments["hookArgs"] && hookArguments["hookArgs"]["projectData"];
if (projectDataHookArg) {
hookArguments["projectData"] = hookArguments[
"$projectData"
] = projectDataHookArg;
hookArguments["projectData"] = hookArguments["$projectData"] =
projectDataHookArg;
}

const maybePromise = this.$injector.resolve(
hookEntryPoint,
hookArguments
hookArguments,
);
if (maybePromise) {
this.$logger.trace("Hook promises to signal completion");
Expand Down Expand Up @@ -255,15 +271,15 @@ export class HooksService implements IHooksService {
"Executing %s hook at location %s with environment ",
hookName,
hook.fullPath,
environment
environment,
);

const output = await this.$childProcess.spawnFromEvent(
command,
[hook.fullPath],
"close",
environment,
{ throwError: false }
{ throwError: false },
);
result = output;

Expand All @@ -275,7 +291,7 @@ export class HooksService implements IHooksService {
"Finished executing %s hook at location %s with environment ",
hookName,
hook.fullPath,
environment
environment,
);
}
const endTime = this.$performanceService.now();
Expand All @@ -289,7 +305,7 @@ export class HooksService implements IHooksService {
private async executeHooksInDirectory(
directoryPath: string,
hookName: string,
hookArguments?: IDictionary<any>
hookArguments?: IDictionary<any>,
): Promise<any[]> {
hookArguments = hookArguments || {};
const results: any[] = [];
Expand All @@ -301,7 +317,7 @@ export class HooksService implements IHooksService {
directoryPath,
hookName,
hook,
hookArguments
hookArguments,
);

if (result) {
Expand All @@ -316,14 +332,14 @@ export class HooksService implements IHooksService {
const hooks: IHook[] = [];
const customHooks: INsConfigHooks[] = this.$projectConfigService.getValue(
"hooks",
[]
[],
);

for (const cHook of customHooks) {
if (cHook.type === hookName) {
const fullPath = path.join(
this.$projectHelper.projectDir,
cHook.script
cHook.script,
);
const isFile = this.$fs.getFsStats(fullPath).isFile();

Expand All @@ -332,8 +348,8 @@ export class HooksService implements IHooksService {
hooks.push(
new Hook(
this.getBaseFilename(fileNameParts[fileNameParts.length - 1]),
fullPath
)
fullPath,
),
);
}
}
Expand All @@ -346,10 +362,10 @@ export class HooksService implements IHooksService {
const allBaseHooks = this.getHooksInDirectory(directoryPath);
const baseHooks = _.filter(
allBaseHooks,
(hook) => hook.name.toLowerCase() === hookName.toLowerCase()
(hook) => hook.name.toLowerCase() === hookName.toLowerCase(),
);
const moreHooks = this.getHooksInDirectory(
path.join(directoryPath, hookName)
path.join(directoryPath, hookName),
);
return baseHooks.concat(moreHooks);
}
Expand Down Expand Up @@ -385,13 +401,11 @@ export class HooksService implements IHooksService {
const clientName = this.$staticConfig.CLIENT_NAME.toUpperCase();

const environment: IStringDictionary = {};
environment[util.format("%s-COMMANDLINE", clientName)] = process.argv.join(
" "
);
environment[util.format("%s-COMMANDLINE", clientName)] =
process.argv.join(" ");
environment[util.format("%s-HOOK_FULL_PATH", clientName)] = hookFullPath;
environment[
util.format("%s-VERSION", clientName)
] = this.$staticConfig.version;
environment[util.format("%s-VERSION", clientName)] =
this.$staticConfig.version;

return {
cwd: this.$projectHelper.projectDir,
Expand Down Expand Up @@ -463,7 +477,7 @@ export class HooksService implements IHooksService {

private validateHookArguments(
hookConstructor: any,
hookFullPath: string
hookFullPath: string,
): string[] {
const invalidArguments: string[] = [];

Expand All @@ -477,7 +491,7 @@ export class HooksService implements IHooksService {
}
} catch (err) {
this.$logger.trace(
`Cannot resolve ${argument} of hook ${hookFullPath}, reason: ${err}`
`Cannot resolve ${argument} of hook ${hookFullPath}, reason: ${err}`,
);
invalidArguments.push(argument);
}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "nativescript",
"main": "./lib/nativescript-cli-lib.js",
"version": "8.9.4",
"version": "9.0.0-alpha.0",
"author": "NativeScript <oss@nativescript.org>",
"description": "Command-line interface for building NativeScript projects",
"bin": {
Expand Down Expand Up @@ -50,7 +50,7 @@
},
"keywords": [
"nativescript",
"telerik",
"typescript",
"mobile"
],
"dependencies": {
Expand Down