svelte-ag 1.0.64 → 1.0.65
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/vite/vite-plugin-component-source-collector.d.ts +0 -6
- package/dist/vite/vite-plugin-component-source-collector.d.ts.map +1 -1
- package/dist/vite/vite-plugin-component-source-collector.js +43 -42
- package/dist/vite/vite-plugin-component-source-collector.unit.test.js +100 -56
- package/package.json +1 -1
- package/src/lib/vite/vite-plugin-component-source-collector.ts +49 -63
- package/src/lib/vite/vite-plugin-component-source-collector.unit.test.ts +117 -66
|
@@ -15,12 +15,6 @@ interface Options {
|
|
|
15
15
|
*/
|
|
16
16
|
safePackages: string[];
|
|
17
17
|
}
|
|
18
|
-
type NormalizeCollectedSourceFilePathOptions = {
|
|
19
|
-
outputFilePath: string;
|
|
20
|
-
root: string;
|
|
21
|
-
safePackages: string[];
|
|
22
|
-
};
|
|
23
|
-
export declare function normalizeCollectedSourceFilePath(file: string, opts: NormalizeCollectedSourceFilePathOptions): Promise<string>;
|
|
24
18
|
export default function componentSourceCollector(opts?: Options): Promise<Plugin>;
|
|
25
19
|
export {};
|
|
26
20
|
//# sourceMappingURL=vite-plugin-component-source-collector.d.ts.map
|
|
@@ -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;AA2CD,wBAA8B,wBAAwB,CAAC,IAAI,GAAE,OAA8B,GAAG,OAAO,CAAC,MAAM,CAAC,CAsM5G"}
|
|
@@ -7,10 +7,14 @@ const componentFiles = new Set();
|
|
|
7
7
|
let firstRound = true;
|
|
8
8
|
const packageJsonCache = new Map();
|
|
9
9
|
function ensureDotRelative(filePath) {
|
|
10
|
-
if (filePath.startsWith('
|
|
10
|
+
if (filePath.startsWith('./'))
|
|
11
11
|
return filePath;
|
|
12
12
|
return `./${filePath}`;
|
|
13
13
|
}
|
|
14
|
+
async function touch(path) {
|
|
15
|
+
const handle = await open(path, 'a');
|
|
16
|
+
await handle.close();
|
|
17
|
+
}
|
|
14
18
|
function isPathInside(parentPath, childPath) {
|
|
15
19
|
const relativePath = relative(parentPath, childPath);
|
|
16
20
|
return relativePath === '' || (!relativePath.startsWith('..') && !isAbsolute(relativePath));
|
|
@@ -35,53 +39,46 @@ async function readPackageNameAt(directory) {
|
|
|
35
39
|
packageJsonCache.set(directory, packageNamePromise);
|
|
36
40
|
return packageNamePromise;
|
|
37
41
|
}
|
|
38
|
-
export async function normalizeCollectedSourceFilePath(file, opts) {
|
|
39
|
-
const cleanedFileName = file.replace(/[?#].*$/, '');
|
|
40
|
-
const resolvedFilePath = isAbsolute(cleanedFileName)
|
|
41
|
-
? resolve(cleanedFileName)
|
|
42
|
-
: resolve(dirname(opts.outputFilePath), cleanedFileName);
|
|
43
|
-
let currentDirectory = dirname(resolvedFilePath);
|
|
44
|
-
while (true) {
|
|
45
|
-
const packageName = await readPackageNameAt(currentDirectory);
|
|
46
|
-
if (packageName !== null) {
|
|
47
|
-
const currentDirectoryPosix = toPosixPath(currentDirectory);
|
|
48
|
-
const isExternalPackage = !isPathInside(opts.root, currentDirectory) || currentDirectoryPosix.includes('/node_modules/');
|
|
49
|
-
if (isExternalPackage && opts.safePackages.includes(packageName)) {
|
|
50
|
-
return resolve(opts.root, 'node_modules', packageName, relative(currentDirectory, resolvedFilePath));
|
|
51
|
-
}
|
|
52
|
-
return resolvedFilePath;
|
|
53
|
-
}
|
|
54
|
-
const parentDirectory = dirname(currentDirectory);
|
|
55
|
-
if (parentDirectory === currentDirectory) {
|
|
56
|
-
return resolvedFilePath;
|
|
57
|
-
}
|
|
58
|
-
currentDirectory = parentDirectory;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
42
|
export default async function componentSourceCollector(opts = { safePackages: [] }) {
|
|
62
43
|
// constants
|
|
63
44
|
const outFileName = opts.outputFile ?? 'component-sources.css';
|
|
64
45
|
const classRegex = /class(?:=|:)/;
|
|
65
46
|
const importRegex = /@import\s+['"]([^'"]+)['"]/g;
|
|
66
|
-
let outputFilePath = undefined;
|
|
67
|
-
let root = undefined;
|
|
68
47
|
// state
|
|
48
|
+
let outputFilePath;
|
|
49
|
+
let nodeModulesPath;
|
|
69
50
|
let config;
|
|
70
51
|
let initialTransformDone = false;
|
|
71
52
|
let initialTransformTimer = null;
|
|
72
|
-
// init
|
|
73
53
|
function shouldAdd(code) {
|
|
74
54
|
return classRegex.test(code);
|
|
75
55
|
}
|
|
56
|
+
async function normalizeCollectedSourceFilePath(file) {
|
|
57
|
+
const cleanedFileName = file.replace(/[?#].*$/, '');
|
|
58
|
+
const resolvedFilePath = isAbsolute(cleanedFileName)
|
|
59
|
+
? resolve(cleanedFileName)
|
|
60
|
+
: resolve(dirname(outputFilePath), cleanedFileName);
|
|
61
|
+
let currentDirectory = dirname(resolvedFilePath);
|
|
62
|
+
while (true) {
|
|
63
|
+
const packageName = await readPackageNameAt(currentDirectory);
|
|
64
|
+
if (packageName !== null) {
|
|
65
|
+
const currentDirectoryPosix = toPosixPath(currentDirectory);
|
|
66
|
+
const isExternalPackage = !isPathInside(dirname(nodeModulesPath), currentDirectory) || currentDirectoryPosix.includes('/node_modules/');
|
|
67
|
+
if (isExternalPackage && opts.safePackages.includes(packageName)) {
|
|
68
|
+
return resolve(nodeModulesPath, packageName, relative(currentDirectory, resolvedFilePath));
|
|
69
|
+
}
|
|
70
|
+
return resolvedFilePath;
|
|
71
|
+
}
|
|
72
|
+
const parentDirectory = dirname(currentDirectory);
|
|
73
|
+
if (parentDirectory === currentDirectory) {
|
|
74
|
+
return resolvedFilePath;
|
|
75
|
+
}
|
|
76
|
+
currentDirectory = parentDirectory;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
76
79
|
async function addPath(file) {
|
|
77
|
-
if (outputFilePath &&
|
|
78
|
-
|
|
79
|
-
root) {
|
|
80
|
-
const normalizedFilePath = await normalizeCollectedSourceFilePath(file, {
|
|
81
|
-
outputFilePath,
|
|
82
|
-
root,
|
|
83
|
-
safePackages: opts.safePackages
|
|
84
|
-
});
|
|
80
|
+
if (outputFilePath && file !== '') {
|
|
81
|
+
const normalizedFilePath = await normalizeCollectedSourceFilePath(file);
|
|
85
82
|
if (!/\.svelte-kit/.test(normalizedFilePath) && // No svelte-kit files
|
|
86
83
|
// No dep files unless marked as safe
|
|
87
84
|
(!/(?:\.pnpm|\.vite)/.test(normalizedFilePath) ||
|
|
@@ -104,10 +101,6 @@ export default async function componentSourceCollector(opts = { safePackages: []
|
|
|
104
101
|
}
|
|
105
102
|
}, 1000); // adjust delay as needed
|
|
106
103
|
}
|
|
107
|
-
async function touch(path) {
|
|
108
|
-
const handle = await open(path, 'a');
|
|
109
|
-
await handle.close();
|
|
110
|
-
}
|
|
111
104
|
const writeOutFile = async () => {
|
|
112
105
|
const lines = Array.from(componentFiles)
|
|
113
106
|
.map((d) => `@source '${d}';`)
|
|
@@ -127,8 +120,16 @@ export default async function componentSourceCollector(opts = { safePackages: []
|
|
|
127
120
|
*/
|
|
128
121
|
async configResolved(resolved) {
|
|
129
122
|
config = resolved;
|
|
130
|
-
|
|
131
|
-
|
|
123
|
+
outputFilePath = resolve(config.root, outFileName);
|
|
124
|
+
let current = config.root;
|
|
125
|
+
while (true) {
|
|
126
|
+
if (await exists(join(current, 'package.json'))) {
|
|
127
|
+
nodeModulesPath = join(current, 'node_modules');
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
else
|
|
131
|
+
current = dirname(current);
|
|
132
|
+
}
|
|
132
133
|
console.log('tailwind-sources:configResolved: Command is', config.command);
|
|
133
134
|
await touch(outputFilePath);
|
|
134
135
|
if (config.command === 'build' && firstRound) {
|
|
@@ -159,7 +160,7 @@ export default async function componentSourceCollector(opts = { safePackages: []
|
|
|
159
160
|
'npm-shrinkwrap.json',
|
|
160
161
|
// pnpm install-state changes:
|
|
161
162
|
'node_modules/.modules.yaml'
|
|
162
|
-
].map((p) => join(root, p));
|
|
163
|
+
].map((p) => join(config.root, p));
|
|
163
164
|
server.watcher.add(lockFiles);
|
|
164
165
|
const onChange = async (file) => {
|
|
165
166
|
if (!lockFiles.includes(file))
|
|
@@ -1,64 +1,108 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import { mkdir, mkdtemp, rm, writeFile } from 'fs/promises';
|
|
3
|
-
import {
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'fs/promises';
|
|
3
|
+
import { join } from 'path';
|
|
4
4
|
import { tmpdir } from 'os';
|
|
5
|
-
import {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
import { build, normalizePath } from 'vite';
|
|
6
|
+
const tempDirectories = [];
|
|
7
|
+
function svelteFixtureLoader() {
|
|
8
|
+
return {
|
|
9
|
+
name: 'svelte-fixture-loader',
|
|
10
|
+
async load(id) {
|
|
11
|
+
if (!id.endsWith('.svelte'))
|
|
12
|
+
return null;
|
|
13
|
+
const source = await readFile(id, 'utf8');
|
|
14
|
+
return `export default ${JSON.stringify(source)};`;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
async function createTempDirectory(prefix) {
|
|
19
|
+
const directory = await mkdtemp(join(tmpdir(), prefix));
|
|
20
|
+
tempDirectories.push(directory);
|
|
10
21
|
return directory;
|
|
11
22
|
}
|
|
12
|
-
async function
|
|
13
|
-
await
|
|
14
|
-
await writeFile(filePath, contents);
|
|
23
|
+
async function writeJson(filePath, value) {
|
|
24
|
+
await writeFile(filePath, JSON.stringify(value, null, 2));
|
|
15
25
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
async function createProjectRoot() {
|
|
27
|
+
const root = await createTempDirectory('vite-plugin-component-source-collector-');
|
|
28
|
+
await mkdir(join(root, 'src'), { recursive: true });
|
|
29
|
+
await mkdir(join(root, 'node_modules'), { recursive: true });
|
|
30
|
+
await writeJson(join(root, 'package.json'), {
|
|
31
|
+
name: 'collector-test-app',
|
|
32
|
+
private: true,
|
|
33
|
+
type: 'module'
|
|
34
|
+
});
|
|
35
|
+
await writeFile(join(root, 'src', 'main.js'), ["import 'safe-pkg/Button.svelte';", "import './app.css';", ''].join('\n'));
|
|
36
|
+
await writeFile(join(root, 'src', 'app.css'), ['@import "safe-pkg/theme.css";', ''].join('\n'));
|
|
37
|
+
return root;
|
|
38
|
+
}
|
|
39
|
+
async function createSafePackage(packageRoot) {
|
|
40
|
+
await mkdir(packageRoot, { recursive: true });
|
|
41
|
+
await writeJson(join(packageRoot, 'package.json'), {
|
|
42
|
+
name: 'safe-pkg',
|
|
43
|
+
version: '1.0.0',
|
|
44
|
+
type: 'module'
|
|
45
|
+
});
|
|
46
|
+
await writeFile(join(packageRoot, 'Button.svelte'), '<button class="pkg-button">Click</button>\n');
|
|
47
|
+
await writeFile(join(packageRoot, 'theme.css'), '.pkg-theme { color: red; }\n');
|
|
48
|
+
}
|
|
49
|
+
async function runCollectorBuild(root) {
|
|
50
|
+
vi.resetModules();
|
|
51
|
+
const { default: componentSourceCollector } = await import('./vite-plugin-component-source-collector.js');
|
|
52
|
+
const collector = await componentSourceCollector({ safePackages: ['safe-pkg'] });
|
|
53
|
+
await build({
|
|
54
|
+
configFile: false,
|
|
55
|
+
logLevel: 'silent',
|
|
56
|
+
publicDir: false,
|
|
57
|
+
resolve: {
|
|
58
|
+
preserveSymlinks: false
|
|
59
|
+
},
|
|
60
|
+
root,
|
|
61
|
+
plugins: [collector, svelteFixtureLoader()],
|
|
62
|
+
build: {
|
|
63
|
+
write: false,
|
|
64
|
+
rollupOptions: {
|
|
65
|
+
input: join(root, 'src', 'main.js')
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const contents = await readFile(join(root, 'component-sources.css'), 'utf8');
|
|
70
|
+
return contents
|
|
71
|
+
.split('\n')
|
|
72
|
+
.map((line) => line.trim())
|
|
73
|
+
.filter(Boolean);
|
|
74
|
+
}
|
|
75
|
+
describe('vite-plugin-component-source-collector', () => {
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
vi.restoreAllMocks();
|
|
78
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
79
|
+
});
|
|
80
|
+
afterEach(async () => {
|
|
81
|
+
vi.restoreAllMocks();
|
|
82
|
+
await Promise.all(tempDirectories.splice(0).map((directory) => rm(directory, {
|
|
83
|
+
recursive: true,
|
|
84
|
+
force: true
|
|
85
|
+
})));
|
|
34
86
|
});
|
|
35
|
-
it('
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const normalizedPath = await normalizeCollectedSourceFilePath(sourceFilePath, {
|
|
44
|
-
outputFilePath,
|
|
45
|
-
root: appRoot,
|
|
46
|
-
safePackages: ['svelte-ag']
|
|
47
|
-
});
|
|
48
|
-
expect(normalizedPath).toBe(join(appRoot, 'node_modules', 'svelte-ag', 'dist', 'lib', 'components', 'dnd', 'DndDroppable.svelte'));
|
|
87
|
+
it('collects safe package component and css sources from installed node_modules packages', async () => {
|
|
88
|
+
const root = await createProjectRoot();
|
|
89
|
+
await createSafePackage(join(root, 'node_modules', 'safe-pkg'));
|
|
90
|
+
const lines = await runCollectorBuild(root);
|
|
91
|
+
expect(lines).toEqual([
|
|
92
|
+
"@source './node_modules/safe-pkg/Button.svelte';",
|
|
93
|
+
"@source './node_modules/safe-pkg/theme.css';"
|
|
94
|
+
]);
|
|
49
95
|
});
|
|
50
|
-
it('
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
});
|
|
62
|
-
expect(normalizedPath).toBe(sourceFilePath);
|
|
96
|
+
it('normalizes symlinked pnpm-style package sources back to node_modules paths', async () => {
|
|
97
|
+
const root = await createProjectRoot();
|
|
98
|
+
const linkedPackageRoot = await createTempDirectory('vite-plugin-component-source-linked-package-');
|
|
99
|
+
await createSafePackage(linkedPackageRoot);
|
|
100
|
+
await symlink(linkedPackageRoot, join(root, 'node_modules', 'safe-pkg'), process.platform === 'win32' ? 'junction' : 'dir');
|
|
101
|
+
const lines = await runCollectorBuild(root);
|
|
102
|
+
expect(lines).toEqual([
|
|
103
|
+
"@source './node_modules/safe-pkg/Button.svelte';",
|
|
104
|
+
"@source './node_modules/safe-pkg/theme.css';"
|
|
105
|
+
]);
|
|
106
|
+
expect(lines.join('\n')).not.toContain(normalizePath(linkedPackageRoot));
|
|
63
107
|
});
|
|
64
108
|
});
|
package/package.json
CHANGED
|
@@ -27,9 +27,13 @@ let firstRound = true;
|
|
|
27
27
|
const packageJsonCache = new Map<string, Promise<string | null>>();
|
|
28
28
|
|
|
29
29
|
function ensureDotRelative(filePath: string): string {
|
|
30
|
-
if (filePath.startsWith('
|
|
30
|
+
if (filePath.startsWith('./')) return filePath;
|
|
31
31
|
return `./${filePath}`;
|
|
32
32
|
}
|
|
33
|
+
async function touch(path: string) {
|
|
34
|
+
const handle = await open(path, 'a');
|
|
35
|
+
await handle.close();
|
|
36
|
+
}
|
|
33
37
|
|
|
34
38
|
function isPathInside(parentPath: string, childPath: string): boolean {
|
|
35
39
|
const relativePath = relative(parentPath, childPath);
|
|
@@ -58,76 +62,56 @@ async function readPackageNameAt(directory: string): Promise<string | null> {
|
|
|
58
62
|
return packageNamePromise;
|
|
59
63
|
}
|
|
60
64
|
|
|
61
|
-
type NormalizeCollectedSourceFilePathOptions = {
|
|
62
|
-
outputFilePath: string;
|
|
63
|
-
root: string;
|
|
64
|
-
safePackages: string[];
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
export async function normalizeCollectedSourceFilePath(
|
|
68
|
-
file: string,
|
|
69
|
-
opts: NormalizeCollectedSourceFilePathOptions
|
|
70
|
-
): Promise<string> {
|
|
71
|
-
const cleanedFileName = file.replace(/[?#].*$/, '');
|
|
72
|
-
const resolvedFilePath = isAbsolute(cleanedFileName)
|
|
73
|
-
? resolve(cleanedFileName)
|
|
74
|
-
: resolve(dirname(opts.outputFilePath), cleanedFileName);
|
|
75
|
-
|
|
76
|
-
let currentDirectory = dirname(resolvedFilePath);
|
|
77
|
-
|
|
78
|
-
while (true) {
|
|
79
|
-
const packageName = await readPackageNameAt(currentDirectory);
|
|
80
|
-
if (packageName !== null) {
|
|
81
|
-
const currentDirectoryPosix = toPosixPath(currentDirectory);
|
|
82
|
-
const isExternalPackage =
|
|
83
|
-
!isPathInside(opts.root, currentDirectory) || currentDirectoryPosix.includes('/node_modules/');
|
|
84
|
-
|
|
85
|
-
if (isExternalPackage && opts.safePackages.includes(packageName)) {
|
|
86
|
-
return resolve(opts.root, 'node_modules', packageName, relative(currentDirectory, resolvedFilePath));
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return resolvedFilePath;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const parentDirectory = dirname(currentDirectory);
|
|
93
|
-
if (parentDirectory === currentDirectory) {
|
|
94
|
-
return resolvedFilePath;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
currentDirectory = parentDirectory;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
65
|
export default async function componentSourceCollector(opts: Options = { safePackages: [] }): Promise<Plugin> {
|
|
102
66
|
// constants
|
|
103
67
|
const outFileName = opts.outputFile ?? 'component-sources.css';
|
|
104
68
|
const classRegex = /class(?:=|:)/;
|
|
105
69
|
const importRegex = /@import\s+['"]([^'"]+)['"]/g;
|
|
106
70
|
|
|
107
|
-
let outputFilePath: string | undefined = undefined;
|
|
108
|
-
let root: string | undefined = undefined;
|
|
109
|
-
|
|
110
71
|
// state
|
|
72
|
+
let outputFilePath: string;
|
|
73
|
+
let nodeModulesPath: string;
|
|
111
74
|
let config: ResolvedConfig;
|
|
112
75
|
let initialTransformDone = false;
|
|
113
76
|
let initialTransformTimer: NodeJS.Timeout | null = null;
|
|
114
77
|
|
|
115
|
-
// init
|
|
116
78
|
function shouldAdd(code: string) {
|
|
117
79
|
return classRegex.test(code);
|
|
118
80
|
}
|
|
119
81
|
|
|
82
|
+
async function normalizeCollectedSourceFilePath(file: string): Promise<string> {
|
|
83
|
+
const cleanedFileName = file.replace(/[?#].*$/, '');
|
|
84
|
+
const resolvedFilePath = isAbsolute(cleanedFileName)
|
|
85
|
+
? resolve(cleanedFileName)
|
|
86
|
+
: resolve(dirname(outputFilePath), cleanedFileName);
|
|
87
|
+
|
|
88
|
+
let currentDirectory = dirname(resolvedFilePath);
|
|
89
|
+
|
|
90
|
+
while (true) {
|
|
91
|
+
const packageName = await readPackageNameAt(currentDirectory);
|
|
92
|
+
if (packageName !== null) {
|
|
93
|
+
const currentDirectoryPosix = toPosixPath(currentDirectory);
|
|
94
|
+
const isExternalPackage =
|
|
95
|
+
!isPathInside(dirname(nodeModulesPath), currentDirectory) || currentDirectoryPosix.includes('/node_modules/');
|
|
96
|
+
|
|
97
|
+
if (isExternalPackage && opts.safePackages.includes(packageName)) {
|
|
98
|
+
return resolve(nodeModulesPath, packageName, relative(currentDirectory, resolvedFilePath));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return resolvedFilePath;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const parentDirectory = dirname(currentDirectory);
|
|
105
|
+
if (parentDirectory === currentDirectory) {
|
|
106
|
+
return resolvedFilePath;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
currentDirectory = parentDirectory;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
120
112
|
async function addPath(file: string) {
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
file !== '' && // No nothing
|
|
124
|
-
root
|
|
125
|
-
) {
|
|
126
|
-
const normalizedFilePath = await normalizeCollectedSourceFilePath(file, {
|
|
127
|
-
outputFilePath,
|
|
128
|
-
root,
|
|
129
|
-
safePackages: opts.safePackages
|
|
130
|
-
});
|
|
113
|
+
if (outputFilePath && file !== '') {
|
|
114
|
+
const normalizedFilePath = await normalizeCollectedSourceFilePath(file);
|
|
131
115
|
|
|
132
116
|
if (
|
|
133
117
|
!/\.svelte-kit/.test(normalizedFilePath) && // No svelte-kit files
|
|
@@ -154,11 +138,6 @@ export default async function componentSourceCollector(opts: Options = { safePac
|
|
|
154
138
|
}, 1000); // adjust delay as needed
|
|
155
139
|
}
|
|
156
140
|
|
|
157
|
-
async function touch(path: string) {
|
|
158
|
-
const handle = await open(path, 'a');
|
|
159
|
-
await handle.close();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
141
|
const writeOutFile = async () => {
|
|
163
142
|
const lines = Array.from(componentFiles)
|
|
164
143
|
.map((d) => `@source '${d}';`)
|
|
@@ -181,8 +160,15 @@ export default async function componentSourceCollector(opts: Options = { safePac
|
|
|
181
160
|
*/
|
|
182
161
|
async configResolved(resolved) {
|
|
183
162
|
config = resolved;
|
|
184
|
-
|
|
185
|
-
|
|
163
|
+
outputFilePath = resolve(config.root, outFileName);
|
|
164
|
+
|
|
165
|
+
let current = config.root;
|
|
166
|
+
while (true) {
|
|
167
|
+
if (await exists(join(current, 'package.json'))) {
|
|
168
|
+
nodeModulesPath = join(current, 'node_modules');
|
|
169
|
+
break;
|
|
170
|
+
} else current = dirname(current);
|
|
171
|
+
}
|
|
186
172
|
|
|
187
173
|
console.log('tailwind-sources:configResolved: Command is', config.command);
|
|
188
174
|
|
|
@@ -216,7 +202,7 @@ export default async function componentSourceCollector(opts: Options = { safePac
|
|
|
216
202
|
'npm-shrinkwrap.json',
|
|
217
203
|
// pnpm install-state changes:
|
|
218
204
|
'node_modules/.modules.yaml'
|
|
219
|
-
].map((p) => join(root!, p));
|
|
205
|
+
].map((p) => join(config.root!, p));
|
|
220
206
|
server.watcher.add(lockFiles);
|
|
221
207
|
const onChange = async (file: string) => {
|
|
222
208
|
if (!lockFiles.includes(file)) return;
|
|
@@ -1,91 +1,142 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import { mkdir, mkdtemp, rm, writeFile } from 'fs/promises';
|
|
3
|
-
import {
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'fs/promises';
|
|
3
|
+
import { join } from 'path';
|
|
4
4
|
import { tmpdir } from 'os';
|
|
5
|
-
import {
|
|
5
|
+
import { build, normalizePath, type Plugin } from 'vite';
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const tempDirectories: string[] = [];
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
function svelteFixtureLoader(): Plugin {
|
|
10
|
+
return {
|
|
11
|
+
name: 'svelte-fixture-loader',
|
|
12
|
+
async load(id) {
|
|
13
|
+
if (!id.endsWith('.svelte')) return null;
|
|
14
|
+
|
|
15
|
+
const source = await readFile(id, 'utf8');
|
|
16
|
+
return `export default ${JSON.stringify(source)};`;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function createTempDirectory(prefix: string): Promise<string> {
|
|
22
|
+
const directory = await mkdtemp(join(tmpdir(), prefix));
|
|
23
|
+
tempDirectories.push(directory);
|
|
12
24
|
return directory;
|
|
13
25
|
}
|
|
14
26
|
|
|
15
|
-
async function
|
|
16
|
-
await
|
|
17
|
-
await writeFile(filePath, contents);
|
|
27
|
+
async function writeJson(filePath: string, value: unknown): Promise<void> {
|
|
28
|
+
await writeFile(filePath, JSON.stringify(value, null, 2));
|
|
18
29
|
}
|
|
19
30
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
31
|
+
async function createProjectRoot(): Promise<string> {
|
|
32
|
+
const root = await createTempDirectory('vite-plugin-component-source-collector-');
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const baseDirectory = await createTemporaryDirectory();
|
|
27
|
-
const appRoot = join(baseDirectory, 'app');
|
|
28
|
-
const outputFilePath = join(appRoot, 'component-sources.css');
|
|
29
|
-
const pnpmPackageRoot = join(
|
|
30
|
-
appRoot,
|
|
31
|
-
'node_modules',
|
|
32
|
-
'.pnpm',
|
|
33
|
-
'svelte-ag@1.0.56_hash',
|
|
34
|
-
'node_modules',
|
|
35
|
-
'svelte-ag'
|
|
36
|
-
);
|
|
37
|
-
const sourceFilePath = join(pnpmPackageRoot, 'dist', 'lib', 'components', 'dnd', 'DndDroppable.svelte');
|
|
34
|
+
await mkdir(join(root, 'src'), { recursive: true });
|
|
35
|
+
await mkdir(join(root, 'node_modules'), { recursive: true });
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
await writeJson(join(root, 'package.json'), {
|
|
38
|
+
name: 'collector-test-app',
|
|
39
|
+
private: true,
|
|
40
|
+
type: 'module'
|
|
41
|
+
});
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
await writeFile(
|
|
44
|
+
join(root, 'src', 'main.js'),
|
|
45
|
+
["import 'safe-pkg/Button.svelte';", "import './app.css';", ''].join('\n')
|
|
46
|
+
);
|
|
47
|
+
await writeFile(join(root, 'src', 'app.css'), ['@import "safe-pkg/theme.css";', ''].join('\n'));
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
return root;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function createSafePackage(packageRoot: string): Promise<void> {
|
|
53
|
+
await mkdir(packageRoot, { recursive: true });
|
|
54
|
+
await writeJson(join(packageRoot, 'package.json'), {
|
|
55
|
+
name: 'safe-pkg',
|
|
56
|
+
version: '1.0.0',
|
|
57
|
+
type: 'module'
|
|
51
58
|
});
|
|
59
|
+
await writeFile(join(packageRoot, 'Button.svelte'), '<button class="pkg-button">Click</button>\n');
|
|
60
|
+
await writeFile(join(packageRoot, 'theme.css'), '.pkg-theme { color: red; }\n');
|
|
61
|
+
}
|
|
52
62
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
async function runCollectorBuild(root: string): Promise<string[]> {
|
|
64
|
+
vi.resetModules();
|
|
65
|
+
const { default: componentSourceCollector } = await import('./vite-plugin-component-source-collector.js');
|
|
66
|
+
const collector = await componentSourceCollector({ safePackages: ['safe-pkg'] });
|
|
67
|
+
|
|
68
|
+
await build({
|
|
69
|
+
configFile: false,
|
|
70
|
+
logLevel: 'silent',
|
|
71
|
+
publicDir: false,
|
|
72
|
+
resolve: {
|
|
73
|
+
preserveSymlinks: false
|
|
74
|
+
},
|
|
75
|
+
root,
|
|
76
|
+
plugins: [collector, svelteFixtureLoader()],
|
|
77
|
+
build: {
|
|
78
|
+
write: false,
|
|
79
|
+
rollupOptions: {
|
|
80
|
+
input: join(root, 'src', 'main.js')
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
59
84
|
|
|
60
|
-
|
|
61
|
-
|
|
85
|
+
const contents = await readFile(join(root, 'component-sources.css'), 'utf8');
|
|
86
|
+
return contents
|
|
87
|
+
.split('\n')
|
|
88
|
+
.map((line) => line.trim())
|
|
89
|
+
.filter(Boolean);
|
|
90
|
+
}
|
|
62
91
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
92
|
+
describe('vite-plugin-component-source-collector', () => {
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
vi.restoreAllMocks();
|
|
95
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
96
|
+
});
|
|
68
97
|
|
|
69
|
-
|
|
70
|
-
|
|
98
|
+
afterEach(async () => {
|
|
99
|
+
vi.restoreAllMocks();
|
|
100
|
+
|
|
101
|
+
await Promise.all(
|
|
102
|
+
tempDirectories.splice(0).map((directory) =>
|
|
103
|
+
rm(directory, {
|
|
104
|
+
recursive: true,
|
|
105
|
+
force: true
|
|
106
|
+
})
|
|
107
|
+
)
|
|
71
108
|
);
|
|
72
109
|
});
|
|
73
110
|
|
|
74
|
-
it('
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
111
|
+
it('collects safe package component and css sources from installed node_modules packages', async () => {
|
|
112
|
+
const root = await createProjectRoot();
|
|
113
|
+
await createSafePackage(join(root, 'node_modules', 'safe-pkg'));
|
|
114
|
+
|
|
115
|
+
const lines = await runCollectorBuild(root);
|
|
79
116
|
|
|
80
|
-
|
|
81
|
-
|
|
117
|
+
expect(lines).toEqual([
|
|
118
|
+
"@source './node_modules/safe-pkg/Button.svelte';",
|
|
119
|
+
"@source './node_modules/safe-pkg/theme.css';"
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('normalizes symlinked pnpm-style package sources back to node_modules paths', async () => {
|
|
124
|
+
const root = await createProjectRoot();
|
|
125
|
+
const linkedPackageRoot = await createTempDirectory('vite-plugin-component-source-linked-package-');
|
|
126
|
+
|
|
127
|
+
await createSafePackage(linkedPackageRoot);
|
|
128
|
+
await symlink(
|
|
129
|
+
linkedPackageRoot,
|
|
130
|
+
join(root, 'node_modules', 'safe-pkg'),
|
|
131
|
+
process.platform === 'win32' ? 'junction' : 'dir'
|
|
132
|
+
);
|
|
82
133
|
|
|
83
|
-
const
|
|
84
|
-
outputFilePath,
|
|
85
|
-
root: appRoot,
|
|
86
|
-
safePackages: ['svelte-ag']
|
|
87
|
-
});
|
|
134
|
+
const lines = await runCollectorBuild(root);
|
|
88
135
|
|
|
89
|
-
expect(
|
|
136
|
+
expect(lines).toEqual([
|
|
137
|
+
"@source './node_modules/safe-pkg/Button.svelte';",
|
|
138
|
+
"@source './node_modules/safe-pkg/theme.css';"
|
|
139
|
+
]);
|
|
140
|
+
expect(lines.join('\n')).not.toContain(normalizePath(linkedPackageRoot));
|
|
90
141
|
});
|
|
91
142
|
});
|