Skip to content
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
31 changes: 22 additions & 9 deletions cli/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,31 +153,42 @@ export async function runHooks(config: Config, platformName: string, dir: string
const allPlugins = await getPlugins(config, platformName);

for (const p of allPlugins) {
await runPlatformHook(config, platformName, p.rootPath, hook);
await runPlatformHook(config, platformName, p.rootPath, hook, { allowNXRootFallback: false });
}
}

interface RunPlatformHookOptions {
allowNXRootFallback?: boolean;
}

export async function runPlatformHook(
config: Config,
platformName: string,
platformDir: string,
hook: string,
options: RunPlatformHookOptions = {},
): Promise<void> {
const { spawn } = await import('child_process');
let pkg;
if (isNXMonorepo(platformDir)) {
pkg = await readJSON(join(findNXMonorepoRoot(platformDir), 'package.json'));
} else {
pkg = await readJSON(join(platformDir, 'package.json'));
const platformPackagePath = join(platformDir, 'package.json');
let cmd: string | undefined;

if (await pathExists(platformPackagePath)) {
const pkg = await readJSON(platformPackagePath);
cmd = pkg.scripts?.[hook];
}

if (!cmd && options.allowNXRootFallback !== false && isNXMonorepo(platformDir)) {
const pkg = await readJSON(join(findNXMonorepoRoot(platformDir), 'package.json'));
cmd = pkg.scripts?.[hook];
}
const cmd = pkg.scripts?.[hook];

if (!cmd) {
return;
}
const hookCommand = cmd;

return new Promise((resolve, reject) => {
const p = spawn(cmd, {
const p = spawn(hookCommand, {
stdio: 'inherit',
shell: true,
cwd: platformDir,
Expand All @@ -195,7 +206,9 @@ export async function runPlatformHook(
resolve();
} else {
reject(
new Error(`${hook} hook on ${platformName} failed with error code: ${code} while running command: ${cmd}`),
new Error(
`${hook} hook on ${platformName} failed with error code: ${code} while running command: ${hookCommand}`,
),
);
}
});
Expand Down
101 changes: 101 additions & 0 deletions cli/test/common.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { mkdirp, pathExists, remove, writeJSON } from 'fs-extra';
import { join } from 'path';
import tmp from 'tmp';

import { runHooks } from '../src/common';
import type { Config } from '../src/definitions';
import { getPlugins } from '../src/plugin';

jest.mock('../src/plugin', () => ({
getPlugins: jest.fn(),
}));

const HOOK = 'capacitor:sync:before';

describe('runHooks', () => {
let cleanupCallback: () => void;
let workspaceDir: string;
let appDir: string;
let pluginDir: string;
let config: Config;

beforeEach(async () => {
const tmpDir = tmp.dirSync({ unsafeCleanup: true });
cleanupCallback = tmpDir.removeCallback;
workspaceDir = tmpDir.name;
appDir = join(workspaceDir, 'apps', 'mobile');
pluginDir = join(workspaceDir, 'node_modules', 'test-plugin');

await mkdirp(appDir);
await mkdirp(pluginDir);
await writeJSON(join(workspaceDir, 'nx.json'), {});
await writeJSON(join(workspaceDir, 'package.json'), {
scripts: {
[HOOK]: markerCommand('root-hook-ran'),
},
});
await writeJSON(join(appDir, 'package.json'), {
scripts: {
[HOOK]: markerCommand('app-hook-ran'),
},
});
await writeJSON(join(pluginDir, 'package.json'), {
scripts: {
[HOOK]: markerCommand('plugin-hook-ran'),
},
});

config = {
app: {
rootDir: appDir,
webDirAbs: join(appDir, 'www'),
extConfig: {},
},
} as Config;

jest.mocked(getPlugins).mockResolvedValue([
{
id: 'test-plugin',
name: 'test-plugin',
version: '1.0.0',
rootPath: pluginDir,
},
]);
});

afterEach(() => {
cleanupCallback();
jest.resetAllMocks();
});

it('runs app and plugin hooks from their own packages in an Nx workspace', async () => {
await runHooks(config, 'android', appDir, HOOK);

expect(await pathExists(join(appDir, 'app-hook-ran'))).toBe(true);
expect(await pathExists(join(pluginDir, 'plugin-hook-ran'))).toBe(true);
expect(await pathExists(join(appDir, 'root-hook-ran'))).toBe(false);
expect(await pathExists(join(pluginDir, 'root-hook-ran'))).toBe(false);
});

it('falls back to the Nx root hook when the app has no package', async () => {
await remove(join(appDir, 'package.json'));
jest.mocked(getPlugins).mockResolvedValue([]);

await runHooks(config, 'android', appDir, HOOK);

expect(await pathExists(join(appDir, 'root-hook-ran'))).toBe(true);
});

it('does not fall back to the Nx root hook for plugins', async () => {
await writeJSON(join(pluginDir, 'package.json'), {});

await runHooks(config, 'android', appDir, HOOK);

expect(await pathExists(join(appDir, 'app-hook-ran'))).toBe(true);
expect(await pathExists(join(pluginDir, 'root-hook-ran'))).toBe(false);
});
});

function markerCommand(marker: string): string {
return `node -e "require('fs').writeFileSync('${marker}', '')"`;
}