diff --git a/apps/daemon/src/deploy.ts b/apps/daemon/src/deploy.ts
index 2c9d20f4a7..951aad84e7 100644
--- a/apps/daemon/src/deploy.ts
+++ b/apps/daemon/src/deploy.ts
@@ -105,6 +105,13 @@ export async function buildDeployFileSet(projectsRoot, projectId, entryName, opt
base: entryBase,
}));
+ // Inline `` text that lives inside a
+ // `',
+ );
+ expect(refs).toEqual([]);
+ });
+
+ it('rewrites url() and @import refs in css content relative to baseDir', () => {
+ expect(
+ rewriteCssReferences(
+ '@import "theme.css";body{background:url("bg.png")}',
+ 'sub',
+ ),
+ ).toBe('@import "sub/theme.css";body{background:url("sub/bg.png")}');
+ });
+
+ it('keeps remote, data, and absolute css refs intact when rewriting', () => {
+ expect(
+ rewriteCssReferences(
+ 'body{background:url("https://cdn.test/a.png");--data:url(data:image/png,abc);--root:url("/abs.png")}',
+ 'sub',
+ ),
+ ).toBe(
+ 'body{background:url("https://cdn.test/a.png");--data:url(data:image/png,abc);--root:url("/abs.png")}',
+ );
+ });
+
+ it('bundles assets referenced from inline ',
+ );
+ await writeFile(path.join(dir, 'theme.css'), 'body{color:red}');
+ await writeFile(path.join(dir, 'assets', 'bg.png'), 'bg');
+ await writeFile(path.join(dir, 'fonts', 'custom.woff2'), 'font');
+
+ const files = await buildDeployFileSet(projectsRoot, projectId, 'index.html');
+
+ expect(files.map((f) => f.file).sort()).toEqual([
+ 'assets/bg.png',
+ 'fonts/custom.woff2',
+ 'index.html',
+ 'theme.css',
+ ]);
+ });
+
+ it('bundles assets referenced from style="" attributes', async () => {
+ const { projectsRoot, projectId, dir } = await setupProject();
+ await mkdir(path.join(dir, 'assets'));
+ await writeFile(
+ path.join(dir, 'index.html'),
+ '
x
',
+ );
+ await writeFile(path.join(dir, 'assets', 'hero.png'), 'hero');
+
+ const files = await buildDeployFileSet(projectsRoot, projectId, 'index.html');
+
+ expect(files.map((f) => f.file).sort()).toEqual(['assets/hero.png', 'index.html']);
+ });
+
+ it('rewrites inline ',
+ );
+ await writeFile(path.join(dir, 'sub', 'assets', 'bg.png'), 'bg');
+
+ const files = await buildDeployFileSet(projectsRoot, projectId, 'sub/page.html');
+ const index = files.find((f) => f.file === 'index.html');
+
+ expect(files.map((f) => f.file).sort()).toEqual(['index.html', 'sub/assets/bg.png']);
+ expect(index?.data.toString('utf8')).toContain('url("sub/assets/bg.png")');
+ });
+
+ it('rewrites style="" url() refs when entry is in a subdirectory', async () => {
+ const { projectsRoot, projectId, dir } = await setupProject();
+ await mkdir(path.join(dir, 'sub'), { recursive: true });
+ await writeFile(
+ path.join(dir, 'sub', 'page.html'),
+ "x
",
+ );
+ await writeFile(path.join(dir, 'sub', 'hero.png'), 'hero');
+
+ const files = await buildDeployFileSet(projectsRoot, projectId, 'sub/page.html');
+ const index = files.find((f) => f.file === 'index.html');
+
+ expect(files.map((f) => f.file).sort()).toEqual(['index.html', 'sub/hero.png']);
+ expect(index?.data.toString('utf8')).toContain("url('sub/hero.png')");
+ });
+
+ it('reports inline ',
+ );
+
+ await expect(
+ buildDeployFileSet(projectsRoot, projectId, 'index.html'),
+ ).rejects.toMatchObject({
+ details: { missing: ['assets/missing.png'] },
+ });
+ });
+
+ it('extracts and rewrites url() refs from ',
+ );
+ await writeFile(path.join(dir, 'sub', 'assets', 'icon.svg'), '');
+
+ const files = await buildDeployFileSet(projectsRoot, projectId, 'sub/page.html');
+ const index = files.find((f) => f.file === 'index.html');
+
+ expect(files.map((f) => f.file).sort()).toEqual(['index.html', 'sub/assets/icon.svg']);
+ expect(index?.data.toString('utf8')).toContain('url("sub/assets/icon.svg")');
+ });
+
+ it('does not rewrite \';';
+ await writeFile(path.join(dir, 'sub', 'page.html'), html);
+
+ const files = await buildDeployFileSet(projectsRoot, projectId, 'sub/page.html');
+ const index = files.find((f) => f.file === 'index.html');
+
+ // The fake ';",
+ );
+ });
+
+ it('does not rewrite -->x
';
+ expect(rewriteEntryHtmlReferences(html, 'sub')).toBe(html);
+ });
+
+ it('runs in linear time on pathological unclosed url(', () => {
+ const huge = '('.repeat(100_000);
+ const input = `body{background:url${huge}}`;
+ const startExtract = Date.now();
+ const refs = extractCssReferences(input);
+ expect(Date.now() - startExtract).toBeLessThan(500);
+ expect(refs).toEqual([]);
+
+ const startRewrite = Date.now();
+ expect(rewriteCssReferences(input, 'sub')).toBe(input);
+ expect(Date.now() - startRewrite).toBeLessThan(500);
+ });
});
describe('deployment link readiness', () => {