Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
__dirname
Browse files Browse the repository at this point in the history
Daniel Del Core committed Sep 22, 2024
1 parent 10ab803 commit 9bf71ed
Showing 11 changed files with 433 additions and 439 deletions.
4 changes: 4 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import path from 'path';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import chalk from 'chalk';
import { Command, Option, CommanderError } from 'commander';

@@ -9,6 +10,9 @@ import init from './init.js';
import validate from './validate.js';
import { InvalidUserInputError, InvalidConfigError } from './errors.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const packageJson = readFileSync(
path.join(__dirname, '..', 'package.json'),
'utf-8',
4 changes: 4 additions & 0 deletions packages/cli/src/main.ts
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import semver from 'semver';
import chalk from 'chalk';
import findUp from 'find-up';
import inquirer from 'inquirer';
import { fileURLToPath } from 'url';
import { PluginManager, PluginManagerOptions } from 'live-plugin-manager';

import * as core from '@hypermod/core';
@@ -21,6 +22,9 @@ import {
import ModuleLoader from './utils/module-loader.js';
import { getConfigPrompt, getMultiConfigPrompt } from './prompt.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default async function main(
paths: string[],
flags: Partial<core.Flags>,
16 changes: 12 additions & 4 deletions packages/cli/src/utils/module-loader.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import path from 'path';
import fs from 'fs-extra';
import { fileURLToPath } from 'url';
import { installPackage } from '@antfu/install-pkg';

import { ModuleLoader } from '@hypermod/fetcher';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

/**
* Register the TSX plugin to allow require TS(X) files.
*/
@@ -14,9 +18,13 @@ const ModuleLoader = (config: {
npmRegistryUrl?: string;
authToken?: string;
}): ModuleLoader => {
const getInfo = (packageName: string) => {
const entryPath = require.resolve(packageName);
const location = entryPath.split(packageName)[0] + packageName;
const getInfo = async (packageName: string) => {
// @ts-expect-error - TS doesn't know about import.meta
const entryPath = await import.meta.resolve(packageName);
const location = (entryPath.split(packageName)[0] + packageName).replace(
'file://',
'',
);
const pkgJsonRaw = fs.readFileSync(
path.join(location, 'package.json'),
'utf8',
@@ -44,7 +52,7 @@ const ModuleLoader = (config: {
],
});

const { pkgJson } = getInfo(packageName);
const { pkgJson } = await getInfo(packageName);

// Install whitelisted devDependencies
if (pkgJson?.hypermod?.dependencies) {
88 changes: 33 additions & 55 deletions packages/core/lib/Worker.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,47 @@
/* eslint-disable @typescript-eslint/no-var-requires */
'use strict';

const path = require('path');
const { EventEmitter } = require('events');
const async = require('neo-async');
const fs = require('graceful-fs');
const writeFileAtomic = require('write-file-atomic');
const { DEFAULT_EXTENSIONS } = require('@babel/core');

const getParser = require('jscodeshift/src/getParser');
const jscodeshift = require('jscodeshift/src/core');

let presetEnv;
try {
presetEnv = require('@babel/preset-env');
} catch (_) {}
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'graceful-fs';
import { EventEmitter } from 'events';
import async from 'neo-async';
import writeFileAtomic from 'write-file-atomic';

import getParser from 'jscodeshift/src/getParser.js';
import jscodeshift from 'jscodeshift/src/core.js';

/**
* Register the TSX plugin to allow require TS(X) files.
*/
import { register } from 'tsx/esm/api';
register();

let emitter;
let finish;
let notify;
let transform;
let parserFromTransform;

if (module.parent) {
if (import.meta.url.replace('file://', '') !== process.argv[1]) {
emitter = new EventEmitter();
// @ts-expect-error
emitter.send = data => run(data);
finish = () => emitter.emit('disconnect');
notify = data => emitter.emit('message', data);

module.exports = args => {
setup(args[0], args[1]);
return emitter;
};
} else {
finish = () => setImmediate(() => process.disconnect());
notify = data => process.send(data);
process.on('message', data => run(data));
setup(process.argv[2], process.argv[3]);
}

// Used by `run-in-band` to run the worker in the same process
export default async function main(args) {
await setup(args[0], args[1]);
return emitter;
}

function prepareJscodeshift(options) {
const parser =
parserFromTransform || getParser(options.parser, options.parserConfig);
@@ -59,59 +61,34 @@ function retrievePath(str) {
}

function getModule(mod) {
return mod.hasOwnProperty('default') ? mod.default : mod;
return Boolean(mod.default) ? mod.default : mod;
}

function setup(entryPath, babel) {
if (babel === 'babel') {
const presets = [];
if (presetEnv) {
presets.push([presetEnv.default, { targets: { node: true } }]);
}

presets.push(require('@babel/preset-typescript').default);

require('@babel/register')({
configFile: false,
babelrc: false,
presets,
plugins: [
require('@babel/plugin-proposal-class-properties').default,
require('@babel/plugin-proposal-nullish-coalescing-operator').default,
require('@babel/plugin-proposal-optional-chaining').default,
require('@babel/plugin-transform-modules-commonjs').default,
],
extensions: [...DEFAULT_EXTENSIONS, '.ts', '.tsx'],
// By default, babel register only compiles things inside the current working directory.
// https://github.com/babel/babel/blob/2a4f16236656178e84b05b8915aab9261c55782c/packages/babel-register/src/node.js#L140-L157
ignore: [
// Ignore parser related files
/@babel\/parser/,
/\/flow-parser\//,
/\/recast\//,
/\/ast-types\//,
],
});
}
async function getModuleName(path) {
const moduleName = retrievePath(path).split('node_modules/')[1];
const pkg = await import(moduleName);
return getModule(pkg);
}

async function setup(entryPath) {
const transformId = retrieveTransformId(entryPath);
const presetId = retrievePresetId(entryPath);

let transformPkg;
let transformModule;

if (transformId) {
transformPkg = getModule(require(path.resolve(retrievePath(entryPath))));
transformPkg = await getModuleName(entryPath);
transformModule = transformPkg.transforms[transformId];
}

if (presetId) {
transformPkg = getModule(require(path.resolve(retrievePath(entryPath))));
transformPkg = await getModuleName(entryPath);
transformModule = transformPkg.presets[presetId];
}

if (!transformId && !presetId) {
transformModule = require(path.resolve(entryPath));
transformModule = await import(path.resolve(entryPath));
}

transform = getModule(transformModule);
@@ -141,6 +118,7 @@ function trimStackTrace(trace) {
// Remove this file from the stack trace of an error thrown in the transformer
const result = [];
trace.split('\n').every(line => {
const __filename = fileURLToPath(import.meta.url);
if (line.indexOf(__filename) === -1) {
result.push(line);
return true;
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
/* eslint-disable @typescript-eslint/no-var-requires */
'use strict';

const fs = require('fs');
const mkdirp = require('mkdirp');
const path = require('path');
const temp = require('temp');

function renameFileTo(oldPath, newFilename) {
import path from 'path';
import fs from 'fs';
// @ts-ignore
import mkdirp from 'mkdirp';
// @ts-ignore
import temp from 'temp';

function renameFileTo(oldPath: string, newFilename: string) {
const projectPath = path.dirname(oldPath);
const newPath = path.join(projectPath, newFilename);
mkdirp.sync(path.dirname(newPath));
fs.renameSync(oldPath, newPath);
return newPath;
}

function createTempFileWith(content, filename, extension) {
function createTempFileWith(
content: string,
filename?: string,
extension?: string,
) {
const info = temp.openSync({ suffix: extension });
let filePath = info.path;
fs.writeSync(info.fd, content);
@@ -27,38 +33,37 @@ function createTempFileWith(content, filename, extension) {

// Test transform files need a js extension to work with @babel/register
// .ts or .tsx work as well
function createTransformWith(content, ext = '.js') {
function createTransformWith(content: string, ext = '.js') {
return createTempFileWith(
'module.exports = function(fileInfo, api, options) { ' + content + ' }',
undefined,
ext,
);
}

function getFileContent(filePath) {
function getFileContent(filePath: string) {
return fs.readFileSync(filePath).toString();
}

describe('Worker API', () => {
it('transforms files', done => {
const worker = require('./Worker');
it('transforms files', async () => {
const worker = await import('./Worker.js');
const transformPath = createTransformWith(
'return fileInfo.source + " changed";',
);
const sourcePath = createTempFileWith('foo');
const emitter = worker([transformPath]);
const emitter = await worker.default([transformPath]);

emitter.send({ files: [sourcePath] });
emitter.once('message', data => {
emitter.once('message', (data: any) => {
expect(data.status).toBe('ok');
expect(data.msg).toBe(sourcePath);
expect(getFileContent(sourcePath)).toBe('foo changed');
done();
});
});

it('transforms files with tranformId as extension', done => {
const worker = require('./Worker');
it('transforms files with tranformId as extension', async () => {
const worker = await import('./Worker.js');
const configPath = createTempFileWith(
`
const transfomer = (fileInfo) => fileInfo.source + " changed";
@@ -68,19 +73,18 @@ describe('Worker API', () => {
'.js',
);
const sourcePath = createTempFileWith('foo');
const emitter = worker([configPath + '@1.0.0']);
const emitter = await worker.default([configPath + '@1.0.0']);

emitter.send({ files: [sourcePath] });
emitter.once('message', data => {
emitter.once('message', (data: any) => {
expect(data.status).toBe('ok');
expect(data.msg).toBe(sourcePath);
expect(getFileContent(sourcePath)).toBe('foo changed');
done();
});
});

it('transforms files with presetId as extension', done => {
const worker = require('./Worker');
it('transforms files with presetId as extension', async () => {
const worker = await import('./Worker.js');
const configPath = createTempFileWith(
`
const transfomer = (fileInfo) => fileInfo.source + " changed";
@@ -90,37 +94,35 @@ describe('Worker API', () => {
'.js',
);
const sourcePath = createTempFileWith('foo');
const emitter = worker([configPath + '#my-preset']);
const emitter = await worker.default([configPath + '#my-preset']);

emitter.send({ files: [sourcePath] });
emitter.once('message', data => {
emitter.once('message', (data: any) => {
expect(data.status).toBe('ok');
expect(data.msg).toBe(sourcePath);
expect(getFileContent(sourcePath)).toBe('foo changed');
done();
});
});

it('passes j as argument', done => {
const worker = require('./Worker');
it('passes j as argument', async () => {
const worker = await import('./Worker.js');
const transformPath = createTempFileWith(
`module.exports = function (file, api) {
return api.j(file.source).toSource() + ' changed';
}`,
);
const sourcePath = createTempFileWith('const x = 10;');

const emitter = worker([transformPath]);
const emitter = await worker.default([transformPath]);
emitter.send({ files: [sourcePath] });
emitter.once('message', data => {
emitter.once('message', (data: any) => {
expect(data.status).toBe('ok');
expect(getFileContent(sourcePath)).toBe('const x = 10;' + ' changed');
done();
});
});

describe('custom parser', () => {
function getTransformForParser(parser) {
function getTransformForParser(parser: string) {
return createTempFileWith(
`function transform(fileInfo, api) {
api.jscodeshift(fileInfo.source);
@@ -136,67 +138,63 @@ describe('Worker API', () => {
return createTempFileWith('const x = (a: Object, b: string): void => {}');
}

it('errors if new flow type code is parsed with babel v5', done => {
const worker = require('./Worker');
it('errors if new flow type code is parsed with babel v5', async () => {
const worker = await import('./Worker.js');
const transformPath = createTransformWith(
'api.jscodeshift(fileInfo.source); return "changed";',
);
const sourcePath = getSourceFile();
const emitter = worker([transformPath]);
const emitter = await worker.default([transformPath]);

emitter.send({ files: [sourcePath] });
emitter.once('message', data => {
emitter.once('message', (data: any) => {
expect(data.status).toBe('error');
expect(data.msg).toMatch('SyntaxError');
done();
});
});

['flow', 'babylon'].forEach(parser => {
it(`uses ${parser} if configured as such`, done => {
const worker = require('./Worker');
it(`uses ${parser} if configured as such`, async () => {
const worker = await import('./Worker.js');
const transformPath = getTransformForParser(parser);
const sourcePath = getSourceFile();
const emitter = worker([transformPath]);
const emitter = await worker.default([transformPath]);

emitter.send({ files: [sourcePath] });
emitter.once('message', data => {
emitter.once('message', (data: any) => {
expect(data.status).toBe('ok');
expect(getFileContent(sourcePath)).toBe('changed');
done();
});
});
});

['babylon', 'flow', 'tsx'].forEach(parser => {
it(`can parse JSX with ${parser}`, done => {
const worker = require('./Worker');
it(`can parse JSX with ${parser}`, async () => {
const worker = await import('./Worker.js');
const transformPath = getTransformForParser(parser);
const sourcePath = createTempFileWith(
'var component = <div>{foobar}</div>;',
);
const emitter = worker([transformPath]);
const emitter = await worker.default([transformPath]);

emitter.send({ files: [sourcePath] });
emitter.once('message', data => {
emitter.once('message', (data: any) => {
expect(data.status).toBe('ok');
expect(getFileContent(sourcePath)).toBe('changed');
done();
});
});
});

it('can parse enums with flow', done => {
const worker = require('./Worker');
it('can parse enums with flow', async () => {
const worker = await import('./Worker.js');
const transformPath = getTransformForParser('flow');
const sourcePath = createTempFileWith('enum E {A, B}');
const emitter = worker([transformPath]);
const emitter = await worker.default([transformPath]);

emitter.send({ files: [sourcePath] });
emitter.once('message', data => {
emitter.once('message', (data: any) => {
expect(data.status).toBe('ok');
expect(getFileContent(sourcePath)).toBe('changed');
done();
});
});
});
8 changes: 0 additions & 8 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -8,14 +8,6 @@
"license": "MIT",
"repository": "https://github.com/hypermod-io/hypermod-community/tree/main/packages/core",
"dependencies": {
"@babel/core": "^7.23.0",
"@babel/parser": "^7.23.0",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8",
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
"@babel/plugin-transform-modules-commonjs": "^7.13.8",
"@babel/preset-typescript": "^7.23.0",
"@babel/register": "^7.23.0",
"@types/neo-async": "^2.6.0",
"@types/write-file-atomic": "^4.0.0",
"chalk": "^4.1.0",
13 changes: 9 additions & 4 deletions packages/core/src/runner.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import child_process from 'child_process';
import chalk from 'chalk';
import { fileURLToPath } from 'url';
import fs from 'graceful-fs';
import path from 'path';
import os from 'os';
@@ -8,6 +9,9 @@ import ignores from 'jscodeshift/src/ignoreFiles';

import { Message, Flags, Statuses } from './types.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

type FileCounters = Record<Statuses, number>;
type Stats = Record<string, number>;

@@ -191,7 +195,7 @@ export function run(
(name: string) =>
!extensions || extensions.indexOf(path.extname(name)) != -1,
)
.then(files => {
.then(async files => {
const numFiles = files.length;

if (numFiles === 0) {
@@ -230,15 +234,16 @@ export function run(
}
}

const args = [entrypointPath, options.babel ? 'babel' : 'no-babel'];

// @ts-expect-error
const Worker = await import('../lib/Worker.js');
const args = [entrypointPath];
const workers = [];

for (let i = 0; i < processes; i++) {
workers.push(
options.runInBand
? // eslint-disable-next-line @typescript-eslint/no-var-requires
require('../lib/Worker')(args)
await Worker.default(args)
: child_process.fork(
path.join(__dirname, '..', 'lib', 'Worker.js'),
args,
21 changes: 10 additions & 11 deletions packages/fetcher/src/index.ts
Original file line number Diff line number Diff line change
@@ -7,12 +7,12 @@ import { Config } from '@hypermod/types';

export interface ModuleLoader {
install: (packageName: string) => Promise<void>;
getInfo: (packageName: string) => {
getInfo: (packageName: string) => Promise<{
location: string;
entryPath: string;
pkgJson: any;
};
require: (packageName: string) => any;
}>;
require: (packageName: string) => Promise<any>;
}

export interface ConfigMeta {
@@ -24,10 +24,9 @@ function resolveConfigExport(pkg: any): Config {
return pkg.default ? pkg.default : pkg;
}

function requireConfig(filePath: string, resolvedPath: string) {
async function requireConfig(filePath: string, resolvedPath: string) {
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pkg = require(resolvedPath);
const pkg = await import(resolvedPath);
return resolveConfigExport(pkg);
} catch (e) {
console.log(resolvedPath, e);
@@ -65,7 +64,7 @@ export async function fetchConfigs(filePath: string): Promise<ConfigMeta[]> {

configs.push({
filePath: matchedPath,
config: requireConfig(matchedPath, resolvedMatchedPath),
config: await requireConfig(matchedPath, resolvedMatchedPath),
});
}

@@ -88,8 +87,8 @@ export async function fetchPackage(
packageManager: ModuleLoader,
): Promise<ConfigMeta> {
await packageManager.install(packageName);
const pkg = packageManager.require(packageName);
const info = packageManager.getInfo(packageName);
const pkg = await packageManager.require(packageName);
const info = await packageManager.getInfo(packageName);

if (!info) {
throw new Error(`Unable to find package info for: ${packageName}`);
@@ -114,7 +113,7 @@ export async function fetchRemotePackage(
let info;

try {
info = packageManager.getInfo(packageName);
info = await packageManager.getInfo(packageName);

if (!info) {
throw new Error();
@@ -127,7 +126,7 @@ export async function fetchRemotePackage(

// Search main entrypoint for transform/presets from the default import
try {
const pkg = packageManager.require(packageName);
const pkg = await packageManager.require(packageName);
const configExport = resolveConfigExport(pkg);

if (configExport.transforms || configExport.presets) {
4 changes: 4 additions & 0 deletions packages/initializer/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import fs from 'fs-extra';
import path from 'path';
import { fileURLToPath } from 'url';
import semver from 'semver';
import * as recast from 'recast';
import { version as cliVersion } from '@hypermod/cli/package.json';
import { version as utilVersion } from '@hypermod/utils/package.json';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const TEMPLATE_PATH = path.join(__dirname, '..', 'template');

export function getPackageJson(packageName: string, version = '0.0.0') {
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
"target": "es6",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"allowJs": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": [
615 changes: 308 additions & 307 deletions yarn.lock

Large diffs are not rendered by default.

0 comments on commit 9bf71ed

Please sign in to comment.