Merged in feature/copy-static-files-via-build-scripts (pull request #2798)
SW-3467 Copy static files via build scripts * add file copy script and add all fonts to design-system * add file copy script and add all fonts to design-system * add file copy script and add all fonts to design-system * remove fonts that will be copied via build scripts * wip * update paths to shared files * update material-symbol script * merge * fix missing shared segment for path in fonts.css Approved-by: Linus Flood
This commit is contained in:
173
scripts/copyFiles.ts
Normal file
173
scripts/copyFiles.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env tsx
|
||||
import { Command } from "commander";
|
||||
import { promises as fs } from "node:fs";
|
||||
import * as path from "node:path";
|
||||
|
||||
async function ensureDir(dir: string): Promise<void> {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
}
|
||||
|
||||
async function copyDir(
|
||||
src: string,
|
||||
dest: string,
|
||||
includeExts: string[] = [],
|
||||
excludeExts: string[] = []
|
||||
): Promise<void> {
|
||||
await ensureDir(dest);
|
||||
const entries = await fs.readdir(src, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.join(src, entry.name);
|
||||
const destPath = path.join(dest, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
await copyDir(srcPath, destPath, includeExts, excludeExts);
|
||||
} else if (entry.isFile()) {
|
||||
if (!shouldCopyFile(entry.name, includeExts, excludeExts)) {
|
||||
continue;
|
||||
}
|
||||
await fs.copyFile(srcPath, destPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseExtList(list?: string): string[] {
|
||||
if (!list) return [];
|
||||
return list
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.map((e) =>
|
||||
e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`
|
||||
);
|
||||
}
|
||||
|
||||
function isMultiPartPattern(pattern: string): boolean {
|
||||
// true for patterns like '.d.ts' or '.spec.ts' (contains an extra dot after the leading one)
|
||||
return pattern.slice(1).includes(".");
|
||||
}
|
||||
|
||||
function matchesPattern(fileNameLower: string, pattern: string): boolean {
|
||||
if (isMultiPartPattern(pattern)) {
|
||||
// match by filename suffix for multi-segment patterns
|
||||
return fileNameLower.endsWith(pattern);
|
||||
}
|
||||
// single-segment patterns: compare the file's last extension exactly (prevents '.ts' matching '.tsx')
|
||||
return path.extname(fileNameLower) === pattern;
|
||||
}
|
||||
|
||||
function shouldCopyFile(
|
||||
fileName: string,
|
||||
includePatterns: string[],
|
||||
excludePatterns: string[]
|
||||
): boolean {
|
||||
const fileLower = fileName.toLowerCase();
|
||||
|
||||
// Exclude takes precedence
|
||||
if (
|
||||
excludePatterns.length > 0 &&
|
||||
excludePatterns.some((p) => matchesPattern(fileLower, p))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If no include patterns provided, include everything not excluded
|
||||
if (includePatterns.length === 0) return true;
|
||||
|
||||
// Otherwise include only if at least one include pattern matches
|
||||
return includePatterns.some((p) => matchesPattern(fileLower, p));
|
||||
}
|
||||
|
||||
async function run(
|
||||
sourceArg: string,
|
||||
destArg: string,
|
||||
includeExts: string[] = [],
|
||||
excludeExts: string[] = []
|
||||
): Promise<{ src: string; dest: string }> {
|
||||
const src = path.resolve(process.cwd(), sourceArg);
|
||||
const dest = path.resolve(process.cwd(), destArg);
|
||||
|
||||
// Validate source exists
|
||||
let stat: any;
|
||||
try {
|
||||
stat = await fs.stat(src);
|
||||
} catch {
|
||||
throw new Error(`Source path does not exist: ${src}`);
|
||||
}
|
||||
if (!stat.isDirectory()) {
|
||||
throw new Error(`Source must be a directory: ${src}`);
|
||||
}
|
||||
|
||||
// Prevent copying into source or a parent of source
|
||||
const rel = path.relative(src, dest);
|
||||
if (!rel || !rel.startsWith("..")) {
|
||||
throw new Error(
|
||||
"Destination must not be inside (or be) the source directory."
|
||||
);
|
||||
}
|
||||
|
||||
await ensureDir(dest);
|
||||
await copyDir(src, dest, includeExts, excludeExts);
|
||||
return { src, dest };
|
||||
}
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name("copy-files")
|
||||
.description(
|
||||
"Recursively copy files from a source directory to a destination directory."
|
||||
)
|
||||
.argument("<source-path>", "Source directory")
|
||||
.argument("<destination-path>", "Destination directory")
|
||||
.option("-q, --quiet", "Suppress success output")
|
||||
.option(
|
||||
"--include-ext <exts>",
|
||||
"Comma-separated list of file extensions to include, e.g. '.js,.css' or 'js,css'"
|
||||
)
|
||||
.option(
|
||||
"--exclude-ext <exts>",
|
||||
"Comma-separated list of file extensions to exclude, e.g. '.map,.tmp' or 'map,tmp'"
|
||||
)
|
||||
.action(
|
||||
async (
|
||||
sourcePath: string,
|
||||
destinationPath: string,
|
||||
options: {
|
||||
quiet?: boolean;
|
||||
includeExt?: string;
|
||||
excludeExt?: string;
|
||||
}
|
||||
) => {
|
||||
try {
|
||||
const includeExts = parseExtList(options.includeExt);
|
||||
const excludeExts = parseExtList(options.excludeExt);
|
||||
const result = await run(
|
||||
sourcePath,
|
||||
destinationPath,
|
||||
includeExts,
|
||||
excludeExts
|
||||
);
|
||||
if (!options.quiet) {
|
||||
console.log(
|
||||
`Copied files from "${result.src}" to "${result.dest}".`
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err instanceof Error ? err.message : String(err));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Show examples in help
|
||||
program.addHelpText(
|
||||
"after",
|
||||
`
|
||||
Examples:
|
||||
tsx scripts/copyFiles.ts static/fonts dist/fonts
|
||||
tsx scripts/copyFiles.ts assets/images build/images
|
||||
tsx scripts/copyFiles.ts src dist --include-ext .js,.css
|
||||
tsx scripts/copyFiles.ts src dist --exclude-ext map,tmp
|
||||
`
|
||||
);
|
||||
|
||||
program.parseAsync(process.argv);
|
||||
Reference in New Issue
Block a user