Skip to content

Commit

Permalink
feat(deno): create deno system to run cli and compiler from deno
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdbradley committed Jun 30, 2020
1 parent 4408ec1 commit b3d79c6
Show file tree
Hide file tree
Showing 13 changed files with 4,656 additions and 0 deletions.
173 changes: 173 additions & 0 deletions src/sys/deno/deno-copy-tasks.ts
@@ -0,0 +1,173 @@
import type * as d from '../../declarations';
import type { Deno } from '../../../types/lib.deno';
import { buildError, catchError, flatOne, normalizePath, unique } from '@utils';
import { basename, dirname, expandGlob, isGlob, isAbsolute, join, resolve } from './deps';

export async function denoCopyTasks(deno: typeof Deno, copyTasks: Required<d.CopyTask>[], srcDir: string) {
const results: d.CopyResults = {
diagnostics: [],
dirPaths: [],
filePaths: [],
};

try {
copyTasks = flatOne(await Promise.all(copyTasks.map(task => processGlobs(task, srcDir))));

copyTasks = unique(copyTasks, task => task.dest);

const allCopyTasks: d.CopyTask[] = [];

// figure out all the file copy tasks we'll have
// by digging down through any directory copy tasks
while (copyTasks.length > 0) {
const tasks = copyTasks.splice(0, 100);

await Promise.all(tasks.map(copyTask => processCopyTask(deno, results, allCopyTasks, copyTask)));
}

// figure out which directories we'll need to make first
const mkDirs = ensureDirs(allCopyTasks);

try {
await Promise.all(mkDirs.map(dir => deno.mkdir(dir, { recursive: true })));
} catch (mkDirErr) {}

while (allCopyTasks.length > 0) {
const tasks = allCopyTasks.splice(0, 100);

await Promise.all(tasks.map(copyTask => deno.copyFile(copyTask.src, copyTask.dest)));
}
} catch (e) {
catchError(results.diagnostics, e);
}

return results;
}

async function processGlobs(copyTask: Required<d.CopyTask>, srcDir: string): Promise<Required<d.CopyTask>[]> {
return isGlob(copyTask.src)
? await processGlobTask(copyTask, srcDir)
: [
{
src: getSrcAbsPath(srcDir, copyTask.src),
dest: copyTask.keepDirStructure ? join(copyTask.dest, copyTask.src) : copyTask.dest,
warn: copyTask.warn,
keepDirStructure: copyTask.keepDirStructure,
},
];
}

function getSrcAbsPath(srcDir: string, src: string) {
if (isAbsolute(src)) {
return src;
}
return join(srcDir, src);
}

async function processGlobTask(copyTask: Required<d.CopyTask>, srcDir: string): Promise<Required<d.CopyTask>[]> {
const copyTasks: Required<d.CopyTask>[] = [];

for await (const walkEntry of expandGlob(copyTask.src, { root: srcDir })) {
const ct = createGlobCopyTask(copyTask, srcDir, walkEntry.name);
copyTasks.push(ct);
}
return copyTasks;
}

function createGlobCopyTask(copyTask: Required<d.CopyTask>, srcDir: string, globRelPath: string): Required<d.CopyTask> {
const dest = join(copyTask.dest, copyTask.keepDirStructure ? globRelPath : basename(globRelPath));
return {
src: join(srcDir, globRelPath),
dest,
warn: copyTask.warn,
keepDirStructure: copyTask.keepDirStructure,
};
}

async function processCopyTask(deno: typeof Deno, results: d.CopyResults, allCopyTasks: d.CopyTask[], copyTask: d.CopyTask) {
try {
copyTask.src = normalizePath(copyTask.src);
copyTask.dest = normalizePath(copyTask.dest);

// get the stats for this src to see if it's a directory or not
const stats = await deno.stat(copyTask.src);
if (stats.isDirectory) {
// still a directory, keep diggin down
if (!results.dirPaths.includes(copyTask.dest)) {
results.dirPaths.push(copyTask.dest);
}

await processCopyTaskDirectory(deno, results, allCopyTasks, copyTask);
} else if (!shouldIgnore(copyTask.src)) {
// this is a file we should copy
if (!results.filePaths.includes(copyTask.dest)) {
results.filePaths.push(copyTask.dest);
}

allCopyTasks.push(copyTask);
}
} catch (e) {
if (copyTask.warn !== false) {
const err = buildError(results.diagnostics);
err.messageText = e.message;
}
}
}

async function processCopyTaskDirectory(deno: typeof Deno, results: d.CopyResults, allCopyTasks: d.CopyTask[], copyTask: d.CopyTask) {
try {
for await (const dirEntry of deno.readDir(copyTask.src)) {
const subCopyTask: d.CopyTask = {
src: join(copyTask.src, dirEntry.name),
dest: join(copyTask.dest, dirEntry.name),
warn: copyTask.warn,
};

await processCopyTask(deno, results, allCopyTasks, subCopyTask);
}
} catch (e) {
catchError(results.diagnostics, e);
}
}

function ensureDirs(copyTasks: d.CopyTask[]) {
const mkDirs: string[] = [];

copyTasks.forEach(copyTask => {
addMkDir(mkDirs, dirname(copyTask.dest));
});

mkDirs.sort((a, b) => {
const partsA = a.split('/').length;
const partsB = b.split('/').length;

if (partsA < partsB) return -1;
if (partsA > partsB) return 1;
if (a < b) return -1;
if (a > b) return 1;
return 0;
});

return mkDirs;
}

function addMkDir(mkDirs: string[], destDir: string) {
destDir = normalizePath(destDir);

if (destDir === ROOT_DIR || destDir + '/' === ROOT_DIR || destDir === '') {
return;
}

if (!mkDirs.includes(destDir)) {
mkDirs.push(destDir);
}
}

const ROOT_DIR = normalizePath(resolve('/'));

function shouldIgnore(filePath: string) {
filePath = filePath.trim().toLowerCase();
return IGNORE.some(ignoreFile => filePath.endsWith(ignoreFile));
}

const IGNORE = ['.ds_store', '.gitignore', 'desktop.ini', 'thumbs.db'];
101 changes: 101 additions & 0 deletions src/sys/deno/deno-load-typescript.ts
@@ -0,0 +1,101 @@
import type * as d from '../../declarations';
import type TypeScript from 'typescript';
import { dependencies } from '../../compiler/sys/dependencies';
import { dirname, resolve } from 'path';
import { noop } from '@utils';
import type { Deno as DenoTypes } from '../../../types/lib.deno';

export const denoLoadTypeScript = (sys: d.CompilerSystem, rootDir: string, typeScriptPath: string): any =>
new Promise(async (promiseResolve, promiseReject) => {
if (!sys) {
promiseReject(`Unable to load TypeScript without Deno sys`);
} else {
try {
const tsDep = dependencies.find(dep => dep.name === 'typescript');

const tsFilePath = typeScriptPath || sys.getLocalModulePath({ rootDir: rootDir, moduleId: tsDep.name, path: tsDep.main });

try {
Deno.statSync(tsFilePath);
} catch (e) {
const tsUrl = sys.getRemoteModuleUrl({ moduleId: tsDep.name, version: tsDep.version, path: tsDep.main });
const rsp = await fetch(tsUrl);
if (rsp.ok) {
try {
Deno.mkdirSync(dirname(tsFilePath), { recursive: true });
} catch (e) {}

const content = await rsp.clone().text();
const encoder = new TextEncoder();
await Deno.writeFile(tsFilePath, encoder.encode(content));
} else {
promiseReject(`unable to fetch: ${tsUrl}`);
return;
}
}

// ensure typescript compiler doesn't think it's nodejs
(globalThis as any).process.browser = true;

// fake cjs module.exports so typescript import gets added to it
const orgModule = (globalThis as any).module;
(globalThis as any).module = { exports: {} };
await import(tsFilePath);

// get the typescript export from the fake cjs module.exports
const importedTs = (globalThis as any).module.exports;

if (orgModule) {
(globalThis as any).module = orgModule;
} else {
delete (globalThis as any).module;
}

// create half-baked sys just to get us going
// later on we'll wire up ts sys w/ the actual stencil sys
const tsSys: TypeScript.System = {
args: [],
createDirectory: noop,
directoryExists: p => {
try {
const s = Deno.statSync(p);
return s.isDirectory;
} catch (e) {}
return false;
},
exit: Deno.exit,
fileExists: p => {
try {
const s = Deno.statSync(p);
return s.isFile;
} catch (e) {}
return false;
},
getCurrentDirectory: Deno.cwd,
getDirectories: () => [],
getExecutingFilePath: () => tsFilePath,
newLine: '\n',
readDirectory: () => [],
readFile: (p, encoding) => {
try {
const decoder = new TextDecoder(encoding);
const data = Deno.readFileSync(p);
return decoder.decode(data);
} catch (e) {}
return undefined;
},
resolvePath: p => resolve(p),
useCaseSensitiveFileNames: Deno.build.os !== 'windows',
write: noop,
writeFile: noop,
};
importedTs.sys = tsSys;

promiseResolve(importedTs);
} catch (e) {
promiseReject(e);
}
}
});

declare const Deno: typeof DenoTypes;
72 changes: 72 additions & 0 deletions src/sys/deno/deno-logger.ts
@@ -0,0 +1,72 @@
import { createTerminalLogger, ColorType, TerminalLoggerSys } from '../../compiler/sys/logger/terminal-logger';
import { bgRed, blue, bold, cyan, dim, gray, green, magenta, red, yellow } from './deps';
import type { Deno as DenoTypes } from '../../../types/lib.deno';

export const createDenoLogger = (c: { Deno: any }) => {
let useColors = true;
const deno: typeof DenoTypes = c.Deno;
const minColumns = 60;
const maxColumns = 120;

const color = (msg: string, colorType: ColorType) => {
if (useColors && !deno.noColor) {
switch (colorType) {
case 'bgRed':
return bgRed(msg);
case 'blue':
return blue(msg);
case 'bold':
return bold(msg);
case 'cyan':
return cyan(msg);
case 'dim':
return dim(msg);
case 'gray':
return gray(msg);
case 'green':
return green(msg);
case 'magenta':
return magenta(msg);
case 'red':
return red(msg);
case 'yellow':
return yellow(msg);
}
}
return msg;
};

const cwd = () => deno.cwd();

const emoji = (e: string) => (deno.build.os !== 'windows' ? e : '');

const enableColors = (enableClrs: boolean) => (useColors = enableClrs);

const getColumns = () => {
const terminalWidth = (deno.stdout && (deno.stdout as any).columns) || 80;
return Math.max(Math.min(maxColumns, terminalWidth), minColumns);
};

const memoryUsage = () => -1;

const relativePath = (_from: string, to: string) => to;

const writeLogs = (logFilePath: string, log: string, append: boolean) => {
const encoder = new TextEncoder();
const data = encoder.encode(log);
deno.writeFileSync(logFilePath, data, { append });
};

const loggerSys: TerminalLoggerSys = {
color,
cwd,
emoji,
enableColors,
getColumns,
memoryUsage,
relativePath,
writeLogs,
};

return createTerminalLogger(loggerSys);
};
45 changes: 45 additions & 0 deletions src/sys/deno/deno-node-compat.ts
@@ -0,0 +1,45 @@
import { createRequire } from 'https://deno.land/std/node/module.ts';
import { join } from 'https://deno.land/std/path/mod.ts';
import * as nodeFs from 'https://deno.land/std/node/fs.ts';
import process from './deno-node-process';
import type { Deno as DenoTypes } from '../../../types/lib.deno';


// idk why the node compat doesn't come with stat and statSync on it??
// https://deno.land/std/node/fs.ts

Object.assign(nodeFs, {
stat: (...args: any[]) => {
const path: string = args[0];
const cb = args.length > 2 ? args[2] : args[1]
try {
const s = Deno.statSync(path);
cb && cb(null, {
isFile: () => s.isFile,
isDirectory: () => s.isDirectory,
isSymbolicLink: () => s.isSymlink,
size: s.size,
});
} catch (e) {
cb && cb(e);
}
},
statSync: (path: string) => {
const s = Deno.statSync(path);
return {
isFile: () => s.isFile,
isDirectory: () => s.isDirectory,
isSymbolicLink: () => s.isSymlink,
size: s.size,
};
}
});

export const applyNodeCompat = (opts: { fromDir: string }) => {
(globalThis as any).process = process;

const nodeRequire = createRequire(join(opts.fromDir, 'noop.js'));
(globalThis as any).require = nodeRequire;
};

declare const Deno: typeof DenoTypes;

0 comments on commit b3d79c6

Please sign in to comment.