-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathadditionalFiles.ts
More file actions
154 lines (129 loc) · 4.74 KB
/
additionalFiles.ts
File metadata and controls
154 lines (129 loc) · 4.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import { BuildManifest } from "@trigger.dev/core/v3";
import { BuildContext } from "@trigger.dev/core/v3/build";
import { copyFile, mkdir } from "node:fs/promises";
import { dirname, isAbsolute, join, posix, relative, resolve } from "node:path";
import { glob } from "tinyglobby";
export type AdditionalFilesOptions = {
files: string[];
/**
* Optional destination directory for the matched files.
*
* When specified, files will be placed under this directory while preserving
* their structure relative to the glob pattern's base directory.
*
* This is useful when including files from parent directories (using `..` in the glob pattern),
* as the default behavior strips `..` segments which can result in unexpected destination paths.
*
* @example
* // In a monorepo with structure: apps/trigger, apps/shared
* // From apps/trigger/trigger.config.ts:
* additionalFiles({
* files: ["../shared/**"],
* destination: "apps/shared"
* })
* // Files from ../shared/utils.ts will be copied to apps/shared/utils.ts
*/
destination?: string;
};
export async function addAdditionalFilesToBuild(
source: string,
options: AdditionalFilesOptions,
context: BuildContext,
manifest: BuildManifest
) {
// Copy any static assets to the destination
const staticAssets = await findStaticAssetFiles(options.files ?? [], manifest.outputPath, {
cwd: context.workingDir,
destination: options.destination,
});
for (const { assets, matcher } of staticAssets) {
if (assets.length === 0) {
context.logger.warn(`[${source}] No files found for matcher`, matcher);
} else {
context.logger.debug(`[${source}] Found ${assets.length} files for matcher`, matcher);
}
}
await copyStaticAssets(staticAssets, source, context);
}
type MatchedStaticAssets = { source: string; destination: string }[];
type FoundStaticAssetFiles = Array<{
matcher: string;
assets: MatchedStaticAssets;
}>;
async function findStaticAssetFiles(
matchers: string[],
destinationPath: string,
options?: { cwd?: string; ignore?: string[]; destination?: string }
): Promise<FoundStaticAssetFiles> {
const result: FoundStaticAssetFiles = [];
for (const matcher of matchers) {
const assets = await findStaticAssetsForMatcher(matcher, destinationPath, options);
result.push({ matcher, assets });
}
return result;
}
// Extracts the base directory from a glob pattern (the non-wildcard prefix).
// For example: "../shared/**" -> "../shared", "./assets/*.txt" -> "./assets"
function getGlobBase(pattern: string): string {
const parts = pattern.split(/[/\\]/);
const baseParts: string[] = [];
for (const part of parts) {
// Stop at the first part that contains glob characters
if (part.includes("*") || part.includes("?") || part.includes("[") || part.includes("{")) {
break;
}
baseParts.push(part);
}
return baseParts.length > 0 ? baseParts.join(posix.sep) : ".";
}
async function findStaticAssetsForMatcher(
matcher: string,
destinationPath: string,
options?: { cwd?: string; ignore?: string[]; destination?: string }
): Promise<MatchedStaticAssets> {
const result: MatchedStaticAssets = [];
const files = await glob({
patterns: [matcher],
cwd: options?.cwd,
ignore: options?.ignore ?? [],
onlyFiles: true,
absolute: true,
});
const cwd = options?.cwd ?? process.cwd();
for (const file of files) {
let pathInsideDestinationDir: string;
if (options?.destination) {
// When destination is specified, compute path relative to the glob pattern's base directory
const globBase = getGlobBase(matcher);
const absoluteGlobBase = isAbsolute(globBase) ? globBase : resolve(cwd, globBase);
const relativeToGlobBase = relative(absoluteGlobBase, file);
// Place files under the specified destination directory
pathInsideDestinationDir = join(options.destination, relativeToGlobBase);
} else {
// Default behavior: compute relative path from cwd and strip ".." segments
pathInsideDestinationDir = relative(cwd, file)
.split(posix.sep)
.filter((p) => p !== "..")
.join(posix.sep);
}
const relativeDestinationPath = join(destinationPath, pathInsideDestinationDir);
result.push({
source: file,
destination: relativeDestinationPath,
});
}
return result;
}
async function copyStaticAssets(
staticAssetFiles: FoundStaticAssetFiles,
sourceName: string,
context: BuildContext
): Promise<void> {
for (const { assets } of staticAssetFiles) {
for (const { source, destination } of assets) {
await mkdir(dirname(destination), { recursive: true });
context.logger.debug(`[${sourceName}] Copying ${source} to ${destination}`);
await copyFile(source, destination);
}
}
}