-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an option to preserve assets in the output directory (#1163)
- Loading branch information
Showing
13 changed files
with
380 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
`); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)} │`), | ||
), | ||
), | ||
), | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.