Skip to content

Commit f89c6a3

Browse files
authoredAug 8, 2024··
feat(compiler): allow ignore pattern for copy task (#5899)
* feat: allow ignore pattern for copy task * add tests to pipeline * fix code docs * remove unnecessary files * add workflow to pipeline * add missing script * fix end-to-end tests * prettier * revert some changes * set absolute flag * use rimraf for support in Windows * chore(deps): install Jest dependencies via TypeScript and Node * fix task for windows * make it relative to source path * tweak
1 parent 70c4e8a commit f89c6a3

21 files changed

+973
-58
lines changed
 

‎.github/workflows/main.yml

+5
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ jobs:
3838
needs: [build_core]
3939
uses: ./.github/workflows/test-bundlers.yml
4040

41+
copytask_tests:
42+
name: Copy Task Tests
43+
needs: [build_core]
44+
uses: ./.github/workflows/test-copytask.yml
45+
4146
component_starter_tests:
4247
name: Component Starter Smoke Test
4348
needs: [build_core]

‎.github/workflows/test-copytask.yml

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Copy Task Tests
2+
3+
on:
4+
workflow_call:
5+
# Make this a reusable workflow, no value needed
6+
# https://docs.github.com/en/actions/using-workflows/reusing-workflows
7+
8+
jobs:
9+
bundler_tests:
10+
name: Verify Copy Task
11+
runs-on: 'ubuntu-22.04'
12+
steps:
13+
- name: Checkout Code
14+
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
15+
16+
- name: Get Core Dependencies
17+
uses: ./.github/workflows/actions/get-core-dependencies
18+
19+
- name: Download Build Archive
20+
uses: ./.github/workflows/actions/download-archive
21+
with:
22+
name: stencil-core
23+
path: .
24+
filename: stencil-core-build.zip
25+
26+
- name: Bundler Tests
27+
run: npm run test.copytask
28+
shell: bash
29+
30+
- name: Check Git Context
31+
uses: ./.github/workflows/actions/check-git-context

‎package-lock.json

+184-21
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+5-3
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@
9696
"build": "npm run clean && npm run tsc.prod && npm run ts scripts/index.ts -- --prod --ci",
9797
"build.watch": "npm run build -- --watch",
9898
"build.updateSelectorEngine": "npm run ts scripts/updateSelectorEngine.ts",
99-
"clean": "rm -rf build/ cli/ compiler/ dev-server/ internal/ mock-doc/ sys/ testing/ && npm run clean:scripts && npm run clean.screenshots",
100-
"clean.screenshots": "rm -rf test/end-to-end/screenshot/builds test/end-to-end/screenshot/images",
101-
"clean:scripts": "rm -rf scripts/build",
99+
"clean": "rimraf build/ cli/ compiler/ dev-server/ internal/ mock-doc/ sys/ testing/ && npm run clean:scripts && npm run clean.screenshots",
100+
"clean.screenshots": "rimraf test/end-to-end/screenshot/builds test/end-to-end/screenshot/images",
101+
"clean:scripts": "rimraf scripts/build",
102102
"lint": "eslint 'bin/*' 'scripts/*.ts' 'scripts/**/*.ts' 'src/*.ts' 'src/**/*.ts' 'src/**/*.tsx' 'test/wdio/**/*.tsx'",
103103
"install.jest": "npx tsx ./src/testing/jest/install-dependencies.mts",
104104
"prettier": "npm run prettier.base -- --write",
@@ -112,6 +112,7 @@
112112
"test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --coverage",
113113
"test.analysis": "cd test && npm run analysis.build-and-analyze",
114114
"test.bundlers": "cd test && npm run bundlers",
115+
"test.copytask": "cd test/copy-task && npm ci && npm run test",
115116
"test.dist": "npm run ts scripts/index.ts -- --validate-build",
116117
"test.end-to-end": "cd test/end-to-end && npm ci && npm test && npm run test.dist",
117118
"test.jest": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
@@ -184,6 +185,7 @@
184185
"prettier": "3.3.1",
185186
"prompts": "2.4.2",
186187
"puppeteer": "^21.0.0",
188+
"rimraf": "^6.0.1",
187189
"rollup": "2.56.3",
188190
"semver": "^7.3.7",
189191
"terser": "5.31.1",

‎src/compiler/output-targets/copy/assets-copy-tasks.ts

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const getComponentAssetsCopyTasks = (
2727
src: assetsMeta.absolutePath,
2828
dest: join(dest, assetsMeta.cmpRelativePath),
2929
warn: false,
30+
ignore: undefined,
3031
keepDirStructure: false,
3132
});
3233
});
@@ -37,6 +38,7 @@ export const getComponentAssetsCopyTasks = (
3738
src: assetsMeta.absolutePath,
3839
dest: collectionDirDestination,
3940
warn: false,
41+
ignore: undefined,
4042
keepDirStructure: false,
4143
});
4244
});

‎src/compiler/output-targets/copy/output-copy.ts

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ import type * as d from '../../../declarations';
55
import { canSkipAssetsCopy, getComponentAssetsCopyTasks } from './assets-copy-tasks';
66
import { getDestAbsPath, getSrcAbsPath } from './local-copy-tasks';
77

8+
const DEFAULT_IGNORE = [
9+
'**/__mocks__/**',
10+
'**/__fixtures__/**',
11+
'**/dist/**',
12+
'**/.{idea,git,cache,output,temp}/**',
13+
'**/.ds_store',
14+
'**/.gitignore',
15+
'**/desktop.ini',
16+
'**/thumbs.db',
17+
];
18+
819
export const outputCopy = async (config: d.ValidatedConfig, compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx) => {
920
const outputTargets = config.outputTargets.filter(isOutputTargetCopy);
1021
if (outputTargets.length === 0) {
@@ -83,6 +94,7 @@ const transformToAbs = (copyTask: d.CopyTask, dest: string): Required<d.CopyTask
8394
return {
8495
src: copyTask.src,
8596
dest: getDestAbsPath(copyTask.src, dest, copyTask.dest),
97+
ignore: copyTask.ignore || DEFAULT_IGNORE,
8698
keepDirStructure:
8799
typeof copyTask.keepDirStructure === 'boolean' ? copyTask.keepDirStructure : copyTask.dest == null,
88100
warn: copyTask.warn !== false,

‎src/declarations/stencil-public-compiler.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1647,6 +1647,11 @@ export interface CopyTask {
16471647
* the output target for which this copy operation is configured.
16481648
*/
16491649
dest?: string;
1650+
/**
1651+
* An optional array of glob patterns to exclude from the copy operation.
1652+
* @default ['**\/__mocks__/**', '**\/__fixtures__/**', '**\/dist/**', '**\/.{idea,git,cache,output,temp}/**', '**\/.ds_store', '**\/.gitignore', '**\/desktop.ini', '**\/thumbs.db']
1653+
*/
1654+
ignore?: string[];
16501655
/**
16511656
* Whether or not Stencil should issue warnings if it cannot find the
16521657
* specified source files or directories. Defaults to `false`.

‎src/sys/node/node-copy-tasks.ts

+19-33
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { buildError, catchError, flatOne, isGlob, normalizePath } from '@utils';
2-
import { glob } from 'glob';
2+
import { glob, type GlobOptions } from 'glob';
33
import path from 'path';
44

55
import type * as d from '../../declarations';
@@ -13,7 +13,7 @@ export async function nodeCopyTasks(copyTasks: Required<d.CopyTask>[], srcDir: s
1313
};
1414

1515
try {
16-
copyTasks = flatOne(await Promise.all(copyTasks.map((task) => processGlobs(task, srcDir))));
16+
copyTasks = flatOne(await Promise.all(copyTasks.map((task) => processGlobTask(task, srcDir))));
1717

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

@@ -44,30 +44,22 @@ export async function nodeCopyTasks(copyTasks: Required<d.CopyTask>[], srcDir: s
4444
return results;
4545
}
4646

47-
async function processGlobs(copyTask: Required<d.CopyTask>, srcDir: string): Promise<Required<d.CopyTask>[]> {
48-
return isGlob(copyTask.src)
49-
? await processGlobTask(copyTask, srcDir)
50-
: [
51-
{
52-
src: getSrcAbsPath(srcDir, copyTask.src),
53-
dest: copyTask.keepDirStructure ? path.join(copyTask.dest, copyTask.src) : copyTask.dest,
54-
warn: copyTask.warn,
55-
keepDirStructure: copyTask.keepDirStructure,
56-
},
57-
];
58-
}
59-
60-
function getSrcAbsPath(srcDir: string, src: string) {
61-
if (path.isAbsolute(src)) {
62-
return src;
63-
}
64-
return path.join(srcDir, src);
65-
}
66-
6747
async function processGlobTask(copyTask: Required<d.CopyTask>, srcDir: string): Promise<Required<d.CopyTask>[]> {
68-
const files = await asyncGlob(copyTask.src, {
48+
/**
49+
* To properly match all files within a certain directory we have to ensure to attach a `/**` to
50+
* the end of the pattern. However we only want to do this if the `src` entry is not a glob pattern
51+
* already or a file with an extension.
52+
*/
53+
const pattern =
54+
isGlob(copyTask.src) || path.extname(copyTask.src).length > 0
55+
? copyTask.src
56+
: './' + path.relative(srcDir, path.join(path.resolve(srcDir, copyTask.src), '**')).replaceAll(path.sep, '/');
57+
58+
const files = await asyncGlob(pattern, {
6959
cwd: srcDir,
7060
nodir: true,
61+
absolute: false,
62+
ignore: copyTask.ignore,
7163
});
7264
return files.map((globRelPath) => createGlobCopyTask(copyTask, srcDir, globRelPath));
7365
}
@@ -77,6 +69,7 @@ function createGlobCopyTask(copyTask: Required<d.CopyTask>, srcDir: string, glob
7769
return {
7870
src: path.join(srcDir, globRelPath),
7971
dest,
72+
ignore: copyTask.ignore,
8073
warn: copyTask.warn,
8174
keepDirStructure: copyTask.keepDirStructure,
8275
};
@@ -96,7 +89,7 @@ async function processCopyTask(results: d.CopyResults, allCopyTasks: d.CopyTask[
9689
}
9790

9891
await processCopyTaskDirectory(results, allCopyTasks, copyTask);
99-
} else if (!shouldIgnore(copyTask.src)) {
92+
} else {
10093
// this is a file we should copy
10194
if (!results.filePaths.includes(copyTask.dest)) {
10295
results.filePaths.push(copyTask.dest);
@@ -169,13 +162,6 @@ function addMkDir(mkDirs: string[], destDir: string) {
169162

170163
const ROOT_DIR = normalizePath(path.resolve('/'));
171164

172-
function shouldIgnore(filePath: string) {
173-
filePath = filePath.trim().toLowerCase();
174-
return IGNORE.some((ignoreFile) => filePath.endsWith(ignoreFile));
175-
}
176-
177-
const IGNORE = ['.ds_store', '.gitignore', 'desktop.ini', 'thumbs.db'];
178-
179-
export function asyncGlob(pattern: string, opts: any) {
180-
return glob(pattern, opts);
165+
export async function asyncGlob(pattern: string, opts: GlobOptions): Promise<string[]> {
166+
return glob(pattern, { ...opts, withFileTypes: false });
181167
}

‎test/copy-task/README.md

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Copy Task Tests
2+
===============
3+
4+
This directory aims to test and validate the behavior for Stencils [Copy Task for Output Targets](https://stenciljs.com/docs/copy-tasks#copy-tasks-for-output-targets). It has a copy task defined in `test/copy-task/stencil.config.ts` and builds this starter projects to then validate if the right files where copies.
5+
6+
## Given
7+
8+
We have a copy task defined as part of an output target, e.g.
9+
10+
```ts
11+
{
12+
type: 'dist-custom-elements',
13+
copy: [{
14+
src: './utils',
15+
dest: './dist/utilsExtra',
16+
}]
17+
}
18+
```
19+
20+
I expect that a `utilsExtra` directory is created that does __not__ copy the following entries:
21+
22+
- files in `__fixtures__` and `__mocks__` directories
23+
- as well as files named `desktop.ini`
24+
25+
Furthermore I expect that no JS files are copied over within the collection directory.

‎test/copy-task/package-lock.json

+516
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎test/copy-task/package.json

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "copytask",
3+
"version": "0.0.1",
4+
"description": "Stencil Component Starter",
5+
"main": "dist/index.cjs.js",
6+
"module": "dist/index.js",
7+
"types": "dist/types/index.d.ts",
8+
"collection": "dist/collection/collection-manifest.json",
9+
"collection:main": "dist/collection/index.js",
10+
"unpkg": "dist/copytask/copytask.esm.js",
11+
"exports": {
12+
".": {
13+
"import": "./dist/copytask/copytask.esm.js",
14+
"require": "./dist/copytask/copytask.cjs.js"
15+
},
16+
"./my-component": {
17+
"import": "./dist/components/my-component.js",
18+
"types": "./dist/components/my-component.d.ts"
19+
},
20+
"./loader": {
21+
"import": "./loader/index.js",
22+
"require": "./loader/index.cjs",
23+
"types": "./loader/index.d.ts"
24+
}
25+
},
26+
"repository": {
27+
"type": "git",
28+
"url": "https://github.com/ionic-team/stencil-component-starter.git"
29+
},
30+
"files": [
31+
"dist/"
32+
],
33+
"scripts": {
34+
"build": "rimraf ./dist && node ../../bin/stencil build",
35+
"test": "npm run build && tsx validate.mts"
36+
},
37+
"devDependencies": {
38+
"rimraf": "^6.0.1"
39+
},
40+
"license": "MIT"
41+
}

‎test/copy-task/src/components.d.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/* eslint-disable */
2+
/* tslint:disable */
3+
/**
4+
* This is an autogenerated file created by the Stencil compiler.
5+
* It contains typing information for all components that exist in this project.
6+
*/
7+
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
8+
export namespace Components {
9+
}
10+
declare global {
11+
interface HTMLElementTagNameMap {
12+
}
13+
}
14+
declare namespace LocalJSX {
15+
interface IntrinsicElements {
16+
}
17+
}
18+
export { LocalJSX as JSX };
19+
declare module "@stencil/core" {
20+
export namespace JSX {
21+
interface IntrinsicElements {
22+
}
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const copyFile = jest.fn().mockImplementation(() => 'JavaScript file content');

‎test/copy-task/src/utils/desktop.ini

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { format } from './utils';
2+
3+
describe('format', () => {
4+
it('returns empty string for no names defined', () => {
5+
expect(format(undefined, undefined, undefined)).toEqual('');
6+
});
7+
8+
it('formats just first names', () => {
9+
expect(format('Joseph', undefined, undefined)).toEqual('Joseph');
10+
});
11+
12+
it('formats first and last names', () => {
13+
expect(format('Joseph', undefined, 'Publique')).toEqual('Joseph Publique');
14+
});
15+
16+
it('formats first, middle and last names', () => {
17+
expect(format('Joseph', 'Quincy', 'Publique')).toEqual('Joseph Quincy Publique');
18+
});
19+
});

‎test/copy-task/src/utils/utils.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function format(first: string, middle: string, last: string): string {
2+
return (first || '') + (middle ? ` ${middle}` : '') + (last ? ` ${last}` : '');
3+
}

‎test/copy-task/stencil.config.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Config } from '@stencil/core';
2+
3+
export const config: Config = {
4+
namespace: 'copytask',
5+
outputTargets: [
6+
{
7+
type: 'dist-custom-elements',
8+
copy: [
9+
{
10+
src: './utils',
11+
dest: './dist/utilsExtra',
12+
},
13+
],
14+
},
15+
{
16+
type: 'dist',
17+
},
18+
],
19+
};

‎test/copy-task/tsconfig.json

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"compilerOptions": {
3+
"allowSyntheticDefaultImports": true,
4+
"allowUnreachableCode": false,
5+
"declaration": false,
6+
"experimentalDecorators": true,
7+
"lib": [
8+
"dom",
9+
"es2017"
10+
],
11+
"moduleResolution": "node",
12+
"module": "esnext",
13+
"target": "es2017",
14+
"noUnusedLocals": true,
15+
"noUnusedParameters": true,
16+
"skipLibCheck": true,
17+
"jsx": "react",
18+
"jsxFactory": "h",
19+
"paths": {
20+
"@stencil/core": [
21+
"../../internal"
22+
],
23+
"@stencil/core/compiler": [
24+
"../../compiler"
25+
],
26+
"@stencil/core/internal": [
27+
"../../internal"
28+
]
29+
}
30+
},
31+
"include": [
32+
"src"
33+
],
34+
"exclude": [
35+
"node_modules",
36+
"src/__mocks__"
37+
]
38+
39+
}

‎test/copy-task/validate.mts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import assert from 'node:assert';
2+
import fs from 'node:fs/promises';
3+
import path from 'node:path';
4+
import url from 'node:url';
5+
6+
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
7+
8+
console.log('Running copy-task validate script');
9+
10+
const utilsExtraFiles = await fs.readdir(path.resolve(__dirname, 'dist', 'utilsExtra'));
11+
assert.equal(
12+
JSON.stringify(utilsExtraFiles),
13+
JSON.stringify(['utils.spec.ts', 'utils.ts'])
14+
);
15+
16+
const copiesMockDirIntoCollection = await fs.access(path.resolve(__dirname, 'dist', 'collection', '__mocks__'))
17+
.then(() => true, () => false);
18+
assert(!copiesMockDirIntoCollection);
19+
20+
console.log(`✅ All assertions passed`);

‎tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"jsx": "react",
1111
"jsxFactory": "h",
1212
"jsxFragmentFactory": "Fragment",
13-
"lib": ["dom", "es2019"],
13+
"lib": ["dom", "es2021"],
1414
"module": "esnext",
1515
"moduleResolution": "node",
1616
"noImplicitAny": true,

0 commit comments

Comments
 (0)
Please sign in to comment.