Skip to content

Commit

Permalink
Add an option to preserve assets in the output directory (#1163)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrm007 committed May 17, 2023
1 parent 404acf6 commit d1b6ba9
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 61 deletions.
24 changes: 24 additions & 0 deletions .changeset/swift-chairs-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
'skuba': minor
---

build, build-package: Add a skuba config key named `assets` to copy assets to the output directory.

In your `package.json`:

```diff
{
"skuba": {
+ "assets": [
+ "**/*.vocab/*translations.json"
+ ],
"entryPoint": "src/index.ts",
"type": "package",
}
}
```

This will instruct skuba to copy the files matching the list of globs to the output directory/ies, preserving the directory structure from the source:

- for `skuba build-package` it will copy them to `lib-commonjs` and `lib-es2015`
- for `skuba build` it will copy them to `tsconfig.json#/compilerOptions.outDir` (`lib` by default)
2 changes: 1 addition & 1 deletion docs/cli/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ skuba build-package
# types │ tsc exited with code 0
```

`skuba lint` runs operations concurrently up to your [CPU core count].
`skuba build-package` runs operations concurrently up to your [CPU core count].
On a resource-constrained Buildkite agent,
you can limit this with the `--serial` flag.
See our [Buildkite guide] for more information.
Expand Down
108 changes: 108 additions & 0 deletions src/cli/build/assets.int.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import memfs, { vol } from 'memfs';

import { copyAssets, copyAssetsConcurrently } from './assets';

jest.mock('fs', () => memfs);
jest.mock('fs-extra', () => memfs);

jest
.spyOn(console, 'log')
.mockImplementation((...args) => stdoutMock(`${args.join(' ')}\n`));

const stdoutMock = jest.fn().mockName('[stdout]');
const getStdOut = () => `${stdoutMock.name}${stdoutMock.mock.calls.join('')}`;

// a snapshot serializer to remove quotes around stdout
expect.addSnapshotSerializer({
test: (val) => typeof val === 'string' && val.startsWith(stdoutMock.name),
print: (val) => (val as string).trim().replace(stdoutMock.name, ''),
});

// the glob in package.json breaks syntax highlighting in VS Code
const justOutDirs = (fs: typeof vol) =>
Object.fromEntries(
Object.entries(fs.toJSON(['.'], {}, true)).filter(
([key]) => !(key.includes('package.json') || key.includes('src/')),
),
);

beforeEach(() => {
vol.reset();
vol.fromJSON({
'package.json': JSON.stringify({
skuba: {
assets: ['**/*.vocab/*translations.json'],
entryPoint: 'src/index.ts',
},
}),
'src/app.ts': '',
'src/.vocab/index.ts': '',
'src/.vocab/translations.json': '',
'src/.vocab/th.translations.json': '',
'src/other.vocab/index.ts': '',
'src/other.vocab/translations.json': '',
'src/other.vocab/th.translations.json': '',
});
jest.clearAllMocks();
});

describe('copyAssets', () => {
it('should copy the assets specified in skuba config to the destination directory', async () => {
await copyAssets('lib');

expect(justOutDirs(vol)).toMatchInlineSnapshot(`
{
"lib/.vocab/th.translations.json": "",
"lib/.vocab/translations.json": "",
"lib/other.vocab/th.translations.json": "",
"lib/other.vocab/translations.json": "",
}
`);
expect(getStdOut()).toMatchInlineSnapshot(`
Copying .vocab/th.translations.json
Copying .vocab/translations.json
Copying other.vocab/th.translations.json
Copying other.vocab/translations.json
`);
});
});

describe('copyAssetsConcurrently', () => {
it('should copy the assets specified in skuba config to the destination directories', async () => {
await copyAssetsConcurrently([
{
outDir: 'lib-commonjs',
name: 'commonjs',
prefixColor: 'green',
},
{
outDir: 'lib-es2015',
name: 'es2015',
prefixColor: 'yellow',
},
]);

expect(justOutDirs(vol)).toMatchInlineSnapshot(`
{
"lib-commonjs/.vocab/th.translations.json": "",
"lib-commonjs/.vocab/translations.json": "",
"lib-commonjs/other.vocab/th.translations.json": "",
"lib-commonjs/other.vocab/translations.json": "",
"lib-es2015/.vocab/th.translations.json": "",
"lib-es2015/.vocab/translations.json": "",
"lib-es2015/other.vocab/th.translations.json": "",
"lib-es2015/other.vocab/translations.json": "",
}
`);
expect(getStdOut()).toMatchInlineSnapshot(`
commonjs │ Copying .vocab/th.translations.json
commonjs │ Copying .vocab/translations.json
commonjs │ Copying other.vocab/th.translations.json
commonjs │ Copying other.vocab/translations.json
es2015 │ Copying .vocab/th.translations.json
es2015 │ Copying .vocab/translations.json
es2015 │ Copying other.vocab/th.translations.json
es2015 │ Copying other.vocab/translations.json
`);
});
});
92 changes: 92 additions & 0 deletions src/cli/build/assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import path from 'path';

import type { Color } from 'chalk';
import chalk from 'chalk';
import fs from 'fs-extra';

import { copyFile } from '../../utils/copy';
import { buildPatternToFilepathMap, crawlDirectory } from '../../utils/dir';
import { type Logger, createLogger, log } from '../../utils/logging';
import {
getConsumerManifest,
getEntryPointFromManifest,
getPropFromConsumerManifest,
} from '../../utils/manifest';

export const copyAssets = async (
destinationDir: string,
logger: Logger = log,
) => {
const manifest = await getConsumerManifest();
if (!manifest) {
return;
}

const assets = await getPropFromConsumerManifest<string, string[]>('assets');
if (!assets) {
return;
}

const entryPoint = await getEntryPointFromManifest();
if (!entryPoint) {
return;
}

const pathSegments = entryPoint.split(path.sep);
const srcDir = pathSegments.length > 1 ? pathSegments[0] : '';
const resolvedSrcDir = path.resolve(path.dirname(manifest.path), srcDir);
const resolvedDestinationDir = path.resolve(
path.dirname(manifest.path),
destinationDir,
);

const allFiles = await crawlDirectory(resolvedSrcDir);
const filesByPattern = buildPatternToFilepathMap(assets, allFiles, {
cwd: resolvedSrcDir,
dot: true,
});
const matchedFiles = Array.from(
new Set(Object.values(filesByPattern).flat()),
);

await Promise.all(
matchedFiles.map(async (filename) => {
logger.subtle(`Copying ${filename}`);

await fs.promises.mkdir(
path.dirname(path.join(resolvedDestinationDir, filename)),
{ recursive: true },
);
await copyFile(
path.join(resolvedSrcDir, filename),
path.join(resolvedDestinationDir, filename),
{ processors: [] },
);
}),
);
};

interface CopyAssetsConfig {
outDir: string;
name: string;
prefixColor: typeof Color;
}

export const copyAssetsConcurrently = async (configs: CopyAssetsConfig[]) => {
const maxNameLength = configs.reduce(
(length, command) => Math.max(length, command.name.length),
0,
);

await Promise.all(
configs.map(({ outDir, name, prefixColor }) =>
copyAssets(
outDir,
createLogger(
false,
chalk[prefixColor](`${name.padEnd(maxNameLength)} │`),
),
),
),
);
};
51 changes: 4 additions & 47 deletions src/cli/build/esbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@ import { inspect } from 'util';

import tsconfigPaths from '@esbuild-plugins/tsconfig-paths';
import { build } from 'esbuild';
import ts, { ModuleKind, ModuleResolutionKind, ScriptTarget } from 'typescript';
import { ModuleKind, ModuleResolutionKind, ScriptTarget } from 'typescript';

import { createLogger } from '../../utils/logging';

import { parseTscArgs } from './args';
import { tsc } from './tsc';

const formatHost: ts.FormatDiagnosticsHost = {
getCanonicalFileName: (fileName) => fileName,
getCurrentDirectory: ts.sys.getCurrentDirectory.bind(undefined),
getNewLine: () => ts.sys.newLine,
};
import { readTsconfig, tsc } from './tsc';

interface EsbuildParameters {
debug: boolean;
Expand All @@ -35,46 +29,9 @@ export const esbuild = async (
return;
}

log.debug(
log.bold(
'tsconfig',
...(tscArgs.project ? ['--project', tscArgs.project] : []),
),
);
log.debug(tscArgs.pathname);

const tsconfigFile = ts.findConfigFile(
tscArgs.dirname,
ts.sys.fileExists.bind(undefined),
tscArgs.basename,
);
if (!tsconfigFile) {
log.err(`Could not find ${tscArgs.pathname}.`);
process.exitCode = 1;
return;
}

const readConfigFile = ts.readConfigFile(
tsconfigFile,
ts.sys.readFile.bind(undefined),
);
if (readConfigFile.error) {
log.err(`Could not read ${tscArgs.pathname}.`);
log.subtle(ts.formatDiagnostic(readConfigFile.error, formatHost));
process.exitCode = 1;
return;
}

const parsedCommandLine = ts.parseJsonConfigFileContent(
readConfigFile.config,
ts.sys,
tscArgs.dirname,
);
const parsedCommandLine = readTsconfig(args, log);

if (parsedCommandLine.errors.length) {
log.err(`Could not parse ${tscArgs.pathname}.`);
log.subtle(ts.formatDiagnostics(parsedCommandLine.errors, formatHost));
process.exitCode = 1;
if (!parsedCommandLine || process.exitCode) {
return;
}

Expand Down
21 changes: 18 additions & 3 deletions src/cli/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { log } from '../../utils/logging';
import { getStringPropFromConsumerManifest } from '../../utils/manifest';
import { tryAddEmptyExports } from '../configure/addEmptyExports';

import { copyAssets } from './assets';
import { esbuild } from './esbuild';
import { tsc } from './tsc';
import { readTsconfig, tsc } from './tsc';

export const build = async (args = process.argv.slice(2)) => {
await tryAddEmptyExports();
Expand All @@ -21,15 +22,15 @@ export const build = async (args = process.argv.slice(2)) => {

log.plain(chalk.yellow('esbuild'));
await esbuild({ debug }, args);
return;
break;
}

// TODO: flip the default case over to `esbuild` in skuba vNext.
case undefined:
case 'tsc': {
log.plain(chalk.blue('tsc'));
await tsc(args);
return;
break;
}

default: {
Expand All @@ -43,4 +44,18 @@ export const build = async (args = process.argv.slice(2)) => {
return;
}
}

const parsedCommandLine = readTsconfig(args, log);

if (!parsedCommandLine || process.exitCode) {
return;
}

const { options: compilerOptions } = parsedCommandLine;

if (!compilerOptions.outDir) {
return;
}

await copyAssets(compilerOptions.outDir);
};
Loading

0 comments on commit d1b6ba9

Please sign in to comment.