svelte-ag 1.0.67 → 1.0.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scripts/resolve-paths.d.ts +29 -0
- package/dist/scripts/resolve-paths.d.ts.map +1 -0
- package/dist/scripts/resolve-paths.js +347 -0
- package/dist/scripts/resolve-paths.unit.test.d.ts +2 -0
- package/dist/scripts/resolve-paths.unit.test.d.ts.map +1 -0
- package/dist/scripts/resolve-paths.unit.test.js +167 -0
- package/dist/vite/vite-plugin-component-source-collector.d.ts.map +1 -1
- package/dist/vite/vite-plugin-component-source-collector.js +8 -17
- package/package.json +5 -1
- package/src/lib/scripts/resolve-paths.ts +470 -0
- package/src/lib/scripts/resolve-paths.unit.test.ts +215 -0
- package/src/lib/vite/vite-plugin-component-source-collector.ts +13 -24
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface ResolvePathsProject {
|
|
2
|
+
tsconfigPath: string;
|
|
3
|
+
rootDir: string;
|
|
4
|
+
srcDir: string;
|
|
5
|
+
distDir: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ResolvePathsOptions {
|
|
8
|
+
cwd?: string;
|
|
9
|
+
excludeAliases?: string[];
|
|
10
|
+
inputs?: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface ResolvePathsWatcher {
|
|
13
|
+
close(): void;
|
|
14
|
+
projects: ResolvePathsProject[];
|
|
15
|
+
}
|
|
16
|
+
interface CliOptions {
|
|
17
|
+
excludeAliases: string[];
|
|
18
|
+
inputs: string[];
|
|
19
|
+
watchMode: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function buildProject(project: ResolvePathsProject, options?: ResolvePathsOptions): Promise<void>;
|
|
22
|
+
export declare function findProjects(options?: ResolvePathsOptions): Promise<ResolvePathsProject[]>;
|
|
23
|
+
export declare function buildProjects(options?: ResolvePathsOptions): Promise<ResolvePathsProject[]>;
|
|
24
|
+
export declare function watchProjects(options?: ResolvePathsOptions): Promise<ResolvePathsWatcher>;
|
|
25
|
+
export declare function parseCliArgs(args: string[]): CliOptions;
|
|
26
|
+
export declare function isDirectExecution(entryPath?: string, moduleUrl?: string): boolean;
|
|
27
|
+
export declare function main(args?: string[]): Promise<void>;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=resolve-paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-paths.d.ts","sourceRoot":"","sources":["../../src/lib/scripts/resolve-paths.ts"],"names":[],"mappings":"AAgCA,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,EAAE,mBAAmB,EAAE,CAAC;CACjC;AAED,UAAU,UAAU;IAClB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;CACpB;AA6HD,wBAAsB,YAAY,CAAC,OAAO,EAAE,mBAAmB,EAAE,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAMjH;AAED,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CA8CpG;AAED,wBAAsB,aAAa,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAcrG;AAgGD,wBAAsB,aAAa,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAenG;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CA0DvD;AAyBD,wBAAgB,iBAAiB,CAAC,SAAS,SAAkB,EAAE,SAAS,SAAkB,GAAG,OAAO,CAMnG;AAED,wBAAsB,IAAI,CAAC,IAAI,WAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAStE"}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { cp, glob, mkdir, rm, stat } from 'node:fs/promises';
|
|
2
|
+
import { realpathSync, watch } from 'node:fs';
|
|
3
|
+
import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { replaceTscAliasPaths } from 'tsc-alias';
|
|
6
|
+
import { loadConfig, prepareConfig } from 'tsc-alias/dist/helpers/config.js';
|
|
7
|
+
import { Output, TrieNode } from 'tsc-alias/dist/utils/index.js';
|
|
8
|
+
const DEFAULT_INPUTS = ['tsconfig.json'];
|
|
9
|
+
const GLOB_EXCLUDES = ['**/node_modules/**', '**/.git/**', '**/.svelte-kit/**', '**/dist/**'];
|
|
10
|
+
const LABEL = '[resolve-paths]';
|
|
11
|
+
const REPLACEABLE_FILE_EXTENSIONS = {
|
|
12
|
+
inputGlob: '{ts,tsx,js,jsx,mjs,cjs,svelte,d.{mts,cts,ts,tsx}}',
|
|
13
|
+
outputCheck: [
|
|
14
|
+
'ts',
|
|
15
|
+
'tsx',
|
|
16
|
+
'js',
|
|
17
|
+
'jsx',
|
|
18
|
+
'mjs',
|
|
19
|
+
'cjs',
|
|
20
|
+
'svelte',
|
|
21
|
+
'json',
|
|
22
|
+
'mts',
|
|
23
|
+
'cts',
|
|
24
|
+
'd.ts',
|
|
25
|
+
'd.tsx',
|
|
26
|
+
'd.mts',
|
|
27
|
+
'd.cts'
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
function formatPath(targetPath, cwd = process.cwd()) {
|
|
31
|
+
return relative(cwd, targetPath) || '.';
|
|
32
|
+
}
|
|
33
|
+
function logInfo(message) {
|
|
34
|
+
console.log(`${LABEL} ${message}`);
|
|
35
|
+
}
|
|
36
|
+
function logError(message) {
|
|
37
|
+
console.error(`${LABEL} ${message}`);
|
|
38
|
+
}
|
|
39
|
+
async function pathExists(targetPath) {
|
|
40
|
+
try {
|
|
41
|
+
await stat(targetPath);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error.code === 'ENOENT') {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function resolveDirectInput(input, cwd) {
|
|
52
|
+
const absoluteInputPath = isAbsolute(input) ? input : resolve(cwd, input);
|
|
53
|
+
if (!(await pathExists(absoluteInputPath))) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
const inputStats = await stat(absoluteInputPath);
|
|
57
|
+
if (inputStats.isDirectory()) {
|
|
58
|
+
const nestedTsconfigPath = resolve(absoluteInputPath, 'tsconfig.json');
|
|
59
|
+
return (await pathExists(nestedTsconfigPath)) ? [nestedTsconfigPath] : [];
|
|
60
|
+
}
|
|
61
|
+
return [absoluteInputPath];
|
|
62
|
+
}
|
|
63
|
+
async function expandInput(input, cwd) {
|
|
64
|
+
const directMatches = await resolveDirectInput(input, cwd);
|
|
65
|
+
if (directMatches.length > 0) {
|
|
66
|
+
return directMatches;
|
|
67
|
+
}
|
|
68
|
+
const matches = await Array.fromAsync(glob(input, {
|
|
69
|
+
cwd,
|
|
70
|
+
exclude: GLOB_EXCLUDES
|
|
71
|
+
}));
|
|
72
|
+
return matches.map((match) => resolve(cwd, match));
|
|
73
|
+
}
|
|
74
|
+
function normalizeAliasPrefix(alias) {
|
|
75
|
+
return alias.replace(/\/\*$/, '').replace(/\/+$/, '');
|
|
76
|
+
}
|
|
77
|
+
function createSilentOutput() {
|
|
78
|
+
return new Output(false, false);
|
|
79
|
+
}
|
|
80
|
+
async function createFilteredAliasTrie(project, excludedAliases) {
|
|
81
|
+
if (excludedAliases.length === 0) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const normalizedExcludedAliases = new Set(excludedAliases.map(normalizeAliasPrefix).filter(Boolean));
|
|
85
|
+
const output = createSilentOutput();
|
|
86
|
+
const loadedConfig = loadConfig(project.tsconfigPath, output);
|
|
87
|
+
if (!loadedConfig.paths) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
const filteredPaths = Object.fromEntries(Object.entries(loadedConfig.paths).filter(([alias]) => !normalizedExcludedAliases.has(normalizeAliasPrefix(alias))));
|
|
91
|
+
const preparedConfig = await prepareConfig({
|
|
92
|
+
configFile: project.tsconfigPath,
|
|
93
|
+
fileExtensions: {
|
|
94
|
+
inputGlob: REPLACEABLE_FILE_EXTENSIONS.inputGlob,
|
|
95
|
+
outputCheck: [...REPLACEABLE_FILE_EXTENSIONS.outputCheck]
|
|
96
|
+
},
|
|
97
|
+
outDir: project.distDir,
|
|
98
|
+
output
|
|
99
|
+
});
|
|
100
|
+
return TrieNode.buildAliasTrie(preparedConfig, filteredPaths);
|
|
101
|
+
}
|
|
102
|
+
async function resolveAliases(project, options = {}) {
|
|
103
|
+
await replaceTscAliasPaths({
|
|
104
|
+
aliasTrie: await createFilteredAliasTrie(project, options.excludeAliases ?? []),
|
|
105
|
+
configFile: project.tsconfigPath,
|
|
106
|
+
outDir: project.distDir,
|
|
107
|
+
fileExtensions: {
|
|
108
|
+
inputGlob: REPLACEABLE_FILE_EXTENSIONS.inputGlob,
|
|
109
|
+
outputCheck: [...REPLACEABLE_FILE_EXTENSIONS.outputCheck]
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async function copyProjectSource(project) {
|
|
114
|
+
await rm(project.distDir, {
|
|
115
|
+
force: true,
|
|
116
|
+
recursive: true
|
|
117
|
+
});
|
|
118
|
+
await mkdir(project.rootDir, { recursive: true });
|
|
119
|
+
await cp(project.srcDir, project.distDir, {
|
|
120
|
+
force: true,
|
|
121
|
+
recursive: true
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
export async function buildProject(project, options = {}) {
|
|
125
|
+
await copyProjectSource(project);
|
|
126
|
+
await resolveAliases(project, {
|
|
127
|
+
...options,
|
|
128
|
+
excludeAliases: [...new Set((options.excludeAliases ?? []).map(normalizeAliasPrefix).filter(Boolean))]
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
export async function findProjects(options = {}) {
|
|
132
|
+
const cwd = options.cwd ?? process.cwd();
|
|
133
|
+
const inputs = options.inputs && options.inputs.length > 0 ? options.inputs : DEFAULT_INPUTS;
|
|
134
|
+
const resolvedTsconfigPaths = new Set();
|
|
135
|
+
for (const input of inputs) {
|
|
136
|
+
const matches = await expandInput(input, cwd);
|
|
137
|
+
if (matches.length === 0) {
|
|
138
|
+
throw new Error(`No tsconfig.json files matched "${input}" from ${formatPath(cwd, cwd)}`);
|
|
139
|
+
}
|
|
140
|
+
for (const match of matches) {
|
|
141
|
+
if (basename(match) !== 'tsconfig.json') {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
resolvedTsconfigPaths.add(resolve(match));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const projects = await Promise.all([...resolvedTsconfigPaths]
|
|
148
|
+
.sort((left, right) => left.localeCompare(right))
|
|
149
|
+
.map(async (tsconfigPath) => {
|
|
150
|
+
const rootDir = dirname(tsconfigPath);
|
|
151
|
+
const srcDir = resolve(rootDir, 'src');
|
|
152
|
+
if (!(await pathExists(srcDir))) {
|
|
153
|
+
throw new Error(`Missing src directory for ${formatPath(tsconfigPath, cwd)} at ${formatPath(srcDir, cwd)}`);
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
distDir: resolve(rootDir, 'dist'),
|
|
157
|
+
rootDir,
|
|
158
|
+
srcDir,
|
|
159
|
+
tsconfigPath
|
|
160
|
+
};
|
|
161
|
+
}));
|
|
162
|
+
if (projects.length === 0) {
|
|
163
|
+
throw new Error(`No tsconfig.json files matched: ${inputs.join(', ')}`);
|
|
164
|
+
}
|
|
165
|
+
return projects;
|
|
166
|
+
}
|
|
167
|
+
export async function buildProjects(options = {}) {
|
|
168
|
+
const cwd = options.cwd ?? process.cwd();
|
|
169
|
+
const projects = await findProjects(options);
|
|
170
|
+
await Promise.all(projects.map(async (project) => {
|
|
171
|
+
await buildProject(project, options);
|
|
172
|
+
logInfo(`updated ${formatPath(project.distDir, cwd)} from ${formatPath(project.srcDir, cwd)} using ${formatPath(project.tsconfigPath, cwd)}`);
|
|
173
|
+
}));
|
|
174
|
+
return projects;
|
|
175
|
+
}
|
|
176
|
+
function createDebouncedProjectRunner(project, cwd, options) {
|
|
177
|
+
let closed = false;
|
|
178
|
+
let activeBuild;
|
|
179
|
+
let pendingReason;
|
|
180
|
+
let timer;
|
|
181
|
+
const run = async () => {
|
|
182
|
+
if (closed) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (activeBuild) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const reason = pendingReason ?? 'change';
|
|
189
|
+
pendingReason = undefined;
|
|
190
|
+
activeBuild = (async () => {
|
|
191
|
+
logInfo(`rebuilding ${formatPath(project.tsconfigPath, cwd)} after ${reason}`);
|
|
192
|
+
await buildProject(project, options);
|
|
193
|
+
logInfo(`watch updated ${formatPath(project.distDir, cwd)}`);
|
|
194
|
+
})();
|
|
195
|
+
try {
|
|
196
|
+
await activeBuild;
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
activeBuild = undefined;
|
|
200
|
+
if (pendingReason) {
|
|
201
|
+
await run();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
return {
|
|
206
|
+
close() {
|
|
207
|
+
closed = true;
|
|
208
|
+
if (timer) {
|
|
209
|
+
clearTimeout(timer);
|
|
210
|
+
timer = undefined;
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
schedule(reason) {
|
|
214
|
+
if (closed) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
pendingReason = reason;
|
|
218
|
+
if (timer) {
|
|
219
|
+
clearTimeout(timer);
|
|
220
|
+
}
|
|
221
|
+
timer = setTimeout(() => {
|
|
222
|
+
timer = undefined;
|
|
223
|
+
void run().catch((error) => {
|
|
224
|
+
logError(`watch rebuild failed for ${formatPath(project.tsconfigPath, cwd)}: ${error.message}`);
|
|
225
|
+
});
|
|
226
|
+
}, 100);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function watchProject(project, cwd, options) {
|
|
231
|
+
const runner = createDebouncedProjectRunner(project, cwd, options);
|
|
232
|
+
const sourceWatcher = watch(project.srcDir, { recursive: true }, (_eventType, fileName) => {
|
|
233
|
+
const suffix = fileName ? ` (${fileName.toString()})` : '';
|
|
234
|
+
runner.schedule(`src change${suffix}`);
|
|
235
|
+
});
|
|
236
|
+
const configWatcher = watch(project.tsconfigPath, () => {
|
|
237
|
+
runner.schedule('tsconfig change');
|
|
238
|
+
});
|
|
239
|
+
return [
|
|
240
|
+
sourceWatcher,
|
|
241
|
+
configWatcher,
|
|
242
|
+
{
|
|
243
|
+
close() {
|
|
244
|
+
runner.close();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
];
|
|
248
|
+
}
|
|
249
|
+
export async function watchProjects(options = {}) {
|
|
250
|
+
const cwd = options.cwd ?? process.cwd();
|
|
251
|
+
const projects = await buildProjects(options);
|
|
252
|
+
const watchers = projects.flatMap((project) => watchProject(project, cwd, options));
|
|
253
|
+
logInfo(`watching ${projects.length} project(s)`);
|
|
254
|
+
return {
|
|
255
|
+
close() {
|
|
256
|
+
for (const watcher of watchers) {
|
|
257
|
+
watcher.close();
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
projects
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
export function parseCliArgs(args) {
|
|
264
|
+
const excludeAliases = [];
|
|
265
|
+
const inputs = [];
|
|
266
|
+
let watchMode = false;
|
|
267
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
268
|
+
const arg = args[index];
|
|
269
|
+
if (arg === '-w' || arg === '--watch') {
|
|
270
|
+
watchMode = true;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (arg === '--exclude-alias') {
|
|
274
|
+
const value = args[index + 1];
|
|
275
|
+
if (!value || value.startsWith('-')) {
|
|
276
|
+
throw new Error('Missing value for --exclude-alias');
|
|
277
|
+
}
|
|
278
|
+
excludeAliases.push(...value
|
|
279
|
+
.split(',')
|
|
280
|
+
.map((item) => item.trim())
|
|
281
|
+
.filter(Boolean));
|
|
282
|
+
index += 1;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (arg.startsWith('--exclude-alias=')) {
|
|
286
|
+
excludeAliases.push(...arg
|
|
287
|
+
.slice('--exclude-alias='.length)
|
|
288
|
+
.split(',')
|
|
289
|
+
.map((item) => item.trim())
|
|
290
|
+
.filter(Boolean));
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (arg === '-h' || arg === '--help') {
|
|
294
|
+
printUsage();
|
|
295
|
+
process.exit(0);
|
|
296
|
+
}
|
|
297
|
+
if (arg.startsWith('-')) {
|
|
298
|
+
throw new Error(`Unknown option "${arg}"`);
|
|
299
|
+
}
|
|
300
|
+
inputs.push(arg);
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
excludeAliases,
|
|
304
|
+
inputs,
|
|
305
|
+
watchMode
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function printUsage() {
|
|
309
|
+
console.log([
|
|
310
|
+
'Usage: resolve-paths [--watch] [tsconfig.json path or glob ...]',
|
|
311
|
+
'',
|
|
312
|
+
'Examples:',
|
|
313
|
+
' resolve-paths',
|
|
314
|
+
' resolve-paths tsconfig.json',
|
|
315
|
+
" resolve-paths --exclude-alias '$shared,$generated' tsconfig.json",
|
|
316
|
+
" resolve-paths 'packages/*/tsconfig.json'",
|
|
317
|
+
" resolve-paths --watch 'packages/*/tsconfig.json'"
|
|
318
|
+
].join('\n'));
|
|
319
|
+
}
|
|
320
|
+
function normalizeExecutablePath(targetPath) {
|
|
321
|
+
try {
|
|
322
|
+
return realpathSync.native(targetPath);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
return resolve(targetPath);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
export function isDirectExecution(entryPath = process.argv[1], moduleUrl = import.meta.url) {
|
|
329
|
+
if (!entryPath) {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
return normalizeExecutablePath(entryPath) === normalizeExecutablePath(fileURLToPath(moduleUrl));
|
|
333
|
+
}
|
|
334
|
+
export async function main(args = process.argv.slice(2)) {
|
|
335
|
+
const { excludeAliases, inputs, watchMode } = parseCliArgs(args);
|
|
336
|
+
if (watchMode) {
|
|
337
|
+
await watchProjects({ excludeAliases, inputs });
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
await buildProjects({ excludeAliases, inputs });
|
|
341
|
+
}
|
|
342
|
+
if (isDirectExecution()) {
|
|
343
|
+
void main().catch((error) => {
|
|
344
|
+
logError(error.message);
|
|
345
|
+
process.exit(1);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-paths.unit.test.d.ts","sourceRoot":"","sources":["../../src/lib/scripts/resolve-paths.unit.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, symlink, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { buildProjects, isDirectExecution, watchProjects } from './resolve-paths.js';
|
|
7
|
+
const tempDirectories = [];
|
|
8
|
+
async function createTempDirectory(prefix) {
|
|
9
|
+
const directory = await mkdtemp(join(tmpdir(), prefix));
|
|
10
|
+
tempDirectories.push(directory);
|
|
11
|
+
return directory;
|
|
12
|
+
}
|
|
13
|
+
async function writeJson(filePath, value) {
|
|
14
|
+
await writeFile(filePath, JSON.stringify(value, null, 2));
|
|
15
|
+
}
|
|
16
|
+
async function createProject(root) {
|
|
17
|
+
await mkdir(join(root, 'src', 'lib'), { recursive: true });
|
|
18
|
+
await mkdir(join(root, 'src', 'components'), { recursive: true });
|
|
19
|
+
await writeJson(join(root, 'tsconfig.json'), {
|
|
20
|
+
compilerOptions: {
|
|
21
|
+
baseUrl: '.',
|
|
22
|
+
paths: {
|
|
23
|
+
'$components/*': ['src/components/*'],
|
|
24
|
+
'$lib/*': ['src/lib/*']
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
await writeFile(join(root, 'src', 'index.ts'), [
|
|
29
|
+
"import { answer } from '../utils';",
|
|
30
|
+
"import Button from '../components/Button.svelte';",
|
|
31
|
+
'',
|
|
32
|
+
'export { answer, Button };',
|
|
33
|
+
''
|
|
34
|
+
].join('\n'));
|
|
35
|
+
await writeFile(join(root, 'src', 'lib', 'utils.ts'), 'export const answer = 42;\n');
|
|
36
|
+
await writeFile(join(root, 'src', 'components', 'Button.svelte'), '<button>Click</button>\n');
|
|
37
|
+
await writeFile(join(root, 'src', 'styles.css'), '.root { color: red; }\n');
|
|
38
|
+
}
|
|
39
|
+
async function createProjectWithExternalAlias(root) {
|
|
40
|
+
const sharedRoot = join(root, '..', 'shared');
|
|
41
|
+
await mkdir(join(root, 'src', 'lib'), { recursive: true });
|
|
42
|
+
await mkdir(sharedRoot, { recursive: true });
|
|
43
|
+
await writeJson(join(root, 'tsconfig.json'), {
|
|
44
|
+
compilerOptions: {
|
|
45
|
+
baseUrl: '.',
|
|
46
|
+
paths: {
|
|
47
|
+
'$lib/*': ['src/lib/*'],
|
|
48
|
+
'$shared/*': ['../shared/*']
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
include: ['src/**/*.ts']
|
|
52
|
+
});
|
|
53
|
+
await writeFile(join(root, 'src', 'index.ts'), [
|
|
54
|
+
"import { answer } from '../utils';",
|
|
55
|
+
"import { sharedAnswer } from '$shared/utils';",
|
|
56
|
+
'',
|
|
57
|
+
'export { answer, sharedAnswer };',
|
|
58
|
+
''
|
|
59
|
+
].join('\n'));
|
|
60
|
+
await writeFile(join(root, 'src', 'lib', 'utils.ts'), 'export const answer = 42;\n');
|
|
61
|
+
await writeFile(join(sharedRoot, 'utils.ts'), 'export const sharedAnswer = 7;\n');
|
|
62
|
+
}
|
|
63
|
+
async function waitFor(assertion, timeoutMs = 5000) {
|
|
64
|
+
const deadline = Date.now() + timeoutMs;
|
|
65
|
+
let lastError;
|
|
66
|
+
while (Date.now() < deadline) {
|
|
67
|
+
try {
|
|
68
|
+
await assertion();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
lastError = error;
|
|
73
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
throw lastError;
|
|
77
|
+
}
|
|
78
|
+
describe('resolve-paths', () => {
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
vi.restoreAllMocks();
|
|
81
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
82
|
+
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
83
|
+
});
|
|
84
|
+
afterEach(async () => {
|
|
85
|
+
vi.restoreAllMocks();
|
|
86
|
+
await Promise.all(tempDirectories.splice(0).map((directory) => rm(directory, {
|
|
87
|
+
force: true,
|
|
88
|
+
recursive: true
|
|
89
|
+
})));
|
|
90
|
+
});
|
|
91
|
+
it('copies a project src tree into dist and resolves tsconfig path aliases', async () => {
|
|
92
|
+
const root = await createTempDirectory('resolve-paths-project-');
|
|
93
|
+
await createProject(root);
|
|
94
|
+
const projects = await buildProjects({
|
|
95
|
+
inputs: [join(root, 'tsconfig.json')]
|
|
96
|
+
});
|
|
97
|
+
expect(projects).toHaveLength(1);
|
|
98
|
+
await expect(readFile(join(root, 'dist', 'lib', 'utils.ts'), 'utf8')).resolves.toBe('export const answer = 42;\n');
|
|
99
|
+
await expect(readFile(join(root, 'dist', 'styles.css'), 'utf8')).resolves.toBe('.root { color: red; }\n');
|
|
100
|
+
const indexSource = await readFile(join(root, 'dist', 'index.ts'), 'utf8');
|
|
101
|
+
expect(indexSource).toContain("from './lib/utils'");
|
|
102
|
+
expect(indexSource).toContain("from './components/Button.svelte'");
|
|
103
|
+
expect(indexSource).not.toContain('$lib/utils');
|
|
104
|
+
expect(indexSource).not.toContain('$components/Button.svelte');
|
|
105
|
+
});
|
|
106
|
+
it('supports globbed tsconfig inputs across multiple projects', async () => {
|
|
107
|
+
const workspaceRoot = await createTempDirectory('resolve-paths-workspace-');
|
|
108
|
+
const packageARoot = join(workspaceRoot, 'packages', 'a');
|
|
109
|
+
const packageBRoot = join(workspaceRoot, 'packages', 'b');
|
|
110
|
+
await createProject(packageARoot);
|
|
111
|
+
await createProject(packageBRoot);
|
|
112
|
+
const projects = await buildProjects({
|
|
113
|
+
cwd: workspaceRoot,
|
|
114
|
+
inputs: ['packages/*/tsconfig.json']
|
|
115
|
+
});
|
|
116
|
+
expect(projects).toHaveLength(2);
|
|
117
|
+
const packageAIndex = await readFile(join(packageARoot, 'dist', 'index.ts'), 'utf8');
|
|
118
|
+
const packageBIndex = await readFile(join(packageBRoot, 'dist', 'index.ts'), 'utf8');
|
|
119
|
+
expect(packageAIndex).toContain("from './lib/utils'");
|
|
120
|
+
expect(packageBIndex).toContain("from './components/Button.svelte'");
|
|
121
|
+
});
|
|
122
|
+
it('does not resolve explicitly excluded aliases', async () => {
|
|
123
|
+
const root = await createTempDirectory('resolve-paths-excluded-alias-');
|
|
124
|
+
await createProjectWithExternalAlias(root);
|
|
125
|
+
await buildProjects({
|
|
126
|
+
excludeAliases: ['$shared'],
|
|
127
|
+
inputs: [join(root, 'tsconfig.json')]
|
|
128
|
+
});
|
|
129
|
+
const indexSource = await readFile(join(root, 'dist', 'index.ts'), 'utf8');
|
|
130
|
+
expect(indexSource).toContain("from './lib/utils'");
|
|
131
|
+
expect(indexSource).toContain("from '$shared/utils'");
|
|
132
|
+
expect(indexSource).not.toContain('../shared/utils');
|
|
133
|
+
});
|
|
134
|
+
it('rebuilds dist in watch mode when src files change or are removed', async () => {
|
|
135
|
+
const root = await createTempDirectory('resolve-paths-watch-');
|
|
136
|
+
await createProject(root);
|
|
137
|
+
const watcher = await watchProjects({
|
|
138
|
+
inputs: [join(root, 'tsconfig.json')]
|
|
139
|
+
});
|
|
140
|
+
try {
|
|
141
|
+
await writeFile(join(root, 'src', 'index.ts'), ["import { answer } from '../utils';", '', 'export const doubled = answer * 2;', ''].join('\n'));
|
|
142
|
+
await unlink(join(root, 'src', 'styles.css'));
|
|
143
|
+
await waitFor(async () => {
|
|
144
|
+
const indexSource = await readFile(join(root, 'dist', 'index.ts'), 'utf8');
|
|
145
|
+
expect(indexSource).toContain("from './lib/utils'");
|
|
146
|
+
expect(indexSource).toContain('export const doubled = answer * 2;');
|
|
147
|
+
expect(indexSource).not.toContain('$lib/utils');
|
|
148
|
+
});
|
|
149
|
+
await waitFor(async () => {
|
|
150
|
+
await expect(readFile(join(root, 'dist', 'styles.css'), 'utf8')).rejects.toMatchObject({
|
|
151
|
+
code: 'ENOENT'
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
watcher.close();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
it('treats symlinked entry paths as direct execution of the same module', async () => {
|
|
160
|
+
const root = await createTempDirectory('resolve-paths-symlink-');
|
|
161
|
+
const actualFilePath = join(root, 'actual.js');
|
|
162
|
+
const symlinkPath = join(root, 'linked.js');
|
|
163
|
+
await writeFile(actualFilePath, 'export {};\n');
|
|
164
|
+
await symlink(actualFilePath, symlinkPath);
|
|
165
|
+
expect(isDirectExecution(symlinkPath, pathToFileURL(actualFilePath).href)).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin-component-source-collector.d.ts","sourceRoot":"","sources":["../../src/lib/vite/vite-plugin-component-source-collector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAkB,MAAM,MAAM,CAAC;AAMnD,UAAU,OAAO;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B;;OAEG;IACH,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;
|
|
1
|
+
{"version":3,"file":"vite-plugin-component-source-collector.d.ts","sourceRoot":"","sources":["../../src/lib/vite/vite-plugin-component-source-collector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAkB,MAAM,MAAM,CAAC;AAMnD,UAAU,OAAO;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B;;OAEG;IACH,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AA4CD,wBAA8B,wBAAwB,CAAC,IAAI,GAAE,OAA8B,GAAG,OAAO,CAAC,MAAM,CAAC,CA2M5G"}
|
|
@@ -42,7 +42,7 @@ async function readPackageNameAt(directory) {
|
|
|
42
42
|
export default async function componentSourceCollector(opts = { safePackages: [] }) {
|
|
43
43
|
// constants
|
|
44
44
|
const outFileName = opts.outputFile ?? 'component-sources.css';
|
|
45
|
-
const classAttributeRegex =
|
|
45
|
+
const classAttributeRegex = /(?:^|[^\w-])(?:className|class)\s*(?:=|:)\s*/;
|
|
46
46
|
const importRegex = /@import\s+['"]([^'"]+)['"]/g;
|
|
47
47
|
// state
|
|
48
48
|
let outputFilePath;
|
|
@@ -93,7 +93,6 @@ export default async function componentSourceCollector(opts = { safePackages: []
|
|
|
93
93
|
const relativeFilePath = toPosixPath(relative(dirname(outputFilePath), normalizedFilePath));
|
|
94
94
|
if (normalizedFilePath === outputFilePath || relativeFilePath === outFileName)
|
|
95
95
|
return;
|
|
96
|
-
// Dont add itself
|
|
97
96
|
componentFiles.add(ensureDotRelative(relativeFilePath));
|
|
98
97
|
}
|
|
99
98
|
function scheduleInitialWrite() {
|
|
@@ -101,14 +100,14 @@ export default async function componentSourceCollector(opts = { safePackages: []
|
|
|
101
100
|
clearTimeout(initialTransformTimer);
|
|
102
101
|
initialTransformTimer = setTimeout(() => {
|
|
103
102
|
if (!initialTransformDone) {
|
|
104
|
-
writeOutFile();
|
|
103
|
+
void writeOutFile();
|
|
105
104
|
initialTransformDone = true;
|
|
106
105
|
}
|
|
107
106
|
}, 1000);
|
|
108
107
|
}
|
|
109
108
|
const writeOutFile = async () => {
|
|
110
109
|
const lines = Array.from(componentFiles)
|
|
111
|
-
.map((
|
|
110
|
+
.map((filePath) => `@source '${filePath}';`)
|
|
112
111
|
.sort();
|
|
113
112
|
if (outputFilePath) {
|
|
114
113
|
const didWrite = await writeIfDifferent(outputFilePath, lines.join('\n'));
|
|
@@ -116,13 +115,9 @@ export default async function componentSourceCollector(opts = { safePackages: []
|
|
|
116
115
|
console.log('tailwind-sources:wrote', lines.length);
|
|
117
116
|
}
|
|
118
117
|
};
|
|
119
|
-
// ---- plugin ---- //
|
|
120
118
|
return {
|
|
121
119
|
name: 'vite-plugin-component-source-collector',
|
|
122
|
-
enforce: 'pre',
|
|
123
|
-
/**
|
|
124
|
-
* Setup. Add exisitng files to internal state if dev
|
|
125
|
-
*/
|
|
120
|
+
enforce: 'pre',
|
|
126
121
|
async configResolved(resolved) {
|
|
127
122
|
config = resolved;
|
|
128
123
|
outputFilePath = resolve(config.root, outFileName);
|
|
@@ -132,8 +127,7 @@ export default async function componentSourceCollector(opts = { safePackages: []
|
|
|
132
127
|
nodeModulesPath = join(current, 'node_modules');
|
|
133
128
|
break;
|
|
134
129
|
}
|
|
135
|
-
|
|
136
|
-
current = dirname(current);
|
|
130
|
+
current = dirname(current);
|
|
137
131
|
}
|
|
138
132
|
console.log('tailwind-sources:configResolved: Command is', config.command);
|
|
139
133
|
await touch(outputFilePath);
|
|
@@ -176,9 +170,8 @@ export default async function componentSourceCollector(opts = { safePackages: []
|
|
|
176
170
|
'bun.lockb',
|
|
177
171
|
'bun.lock',
|
|
178
172
|
'npm-shrinkwrap.json',
|
|
179
|
-
// pnpm install-state changes:
|
|
180
173
|
'node_modules/.modules.yaml'
|
|
181
|
-
].map((
|
|
174
|
+
].map((path) => join(config.root, path));
|
|
182
175
|
server.watcher.add(lockFiles);
|
|
183
176
|
const onChange = async (file) => {
|
|
184
177
|
if (!lockFiles.includes(file))
|
|
@@ -198,7 +191,6 @@ export default async function componentSourceCollector(opts = { safePackages: []
|
|
|
198
191
|
if (id.includes('css') && code.includes('@import')) {
|
|
199
192
|
const matches = code.matchAll(importRegex);
|
|
200
193
|
for (const match of matches) {
|
|
201
|
-
// console.log('MATching', match);
|
|
202
194
|
try {
|
|
203
195
|
const resolved = await this.resolve(match[1], id);
|
|
204
196
|
if (resolved) {
|
|
@@ -206,11 +198,10 @@ export default async function componentSourceCollector(opts = { safePackages: []
|
|
|
206
198
|
}
|
|
207
199
|
}
|
|
208
200
|
catch {
|
|
209
|
-
//
|
|
201
|
+
// Ignore unresolved CSS imports while building the Tailwind source list.
|
|
210
202
|
}
|
|
211
203
|
}
|
|
212
204
|
}
|
|
213
|
-
// Adds all other files with the classRegex
|
|
214
205
|
if (shouldAdd(code, id)) {
|
|
215
206
|
await addPath(id);
|
|
216
207
|
}
|
|
@@ -218,7 +209,7 @@ export default async function componentSourceCollector(opts = { safePackages: []
|
|
|
218
209
|
scheduleInitialWrite();
|
|
219
210
|
}
|
|
220
211
|
},
|
|
221
|
-
async handleHotUpdate(
|
|
212
|
+
async handleHotUpdate() {
|
|
222
213
|
await writeOutFile();
|
|
223
214
|
},
|
|
224
215
|
async buildEnd() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-ag",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.69",
|
|
4
4
|
"description": "Useful svelte components",
|
|
5
5
|
"bugs": "https://github.com/ageorgeh/svelte-ag/issues",
|
|
6
6
|
"repository": {
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"default": "./dist/*"
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"resolve-paths": "dist/scripts/resolve-paths.js"
|
|
30
|
+
},
|
|
28
31
|
"files": [
|
|
29
32
|
"./src/lib",
|
|
30
33
|
"./dist"
|
|
@@ -60,6 +63,7 @@
|
|
|
60
63
|
"tailwind-merge": "^3.5.0",
|
|
61
64
|
"tailwind-variants": "^3.2.2",
|
|
62
65
|
"ts-ag": "^1.1.14",
|
|
66
|
+
"tsc-alias": "^1.8.16",
|
|
63
67
|
"valibot": "^1.3.1"
|
|
64
68
|
},
|
|
65
69
|
"devDependencies": {
|