• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

marekdedic / rollup-plugin-htaccess / 9980645395

17 Jul 2024 07:58PM UTC coverage: 87.805% (-11.0%) from 98.805%
9980645395

Pull #50

github

marekdedic
Not writing test Vite build output to disk
Pull Request #50: Added the ability to extract CSP from HTML <meta> tags

221 of 238 branches covered (92.86%)

3 of 35 new or added lines in 1 file covered. (8.57%)

252 of 287 relevant lines covered (87.8%)

272.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

8.57
/src/extractMetaCSP.ts
1
import { findAll } from "domutils";
2
import { readFile, writeFile } from "fs";
3
import { ElementType, parseDocument } from "htmlparser2";
4
import { join } from "path";
5
import type { OutputOptions, PluginHooks } from "rollup";
6

7
import type { Options } from "./index";
8
import { escapeValue } from "./utils";
9

10
/**
11
 * @public
12
 */
13
export interface ExtractMetaCSPEnabledOptions {
14
  enabled: true;
15
  htaccessFile?: string;
16
  files: Array<string>;
17
}
18

19
/**
20
 * @public
21
 */
22
export type ExtractMetaCSPOptions =
23
  | ExtractMetaCSPEnabledOptions
24
  | { enabled: false };
25

26
async function asyncReadFile(path: string): Promise<string | null> {
NEW
27
  return new Promise<string | null>((resolve) => {
×
NEW
28
    readFile(path, "utf8", (err, data) => {
×
NEW
29
      if (err !== null) {
×
NEW
30
        resolve(null);
×
31
      }
NEW
32
      resolve(data);
×
33
    });
34
  });
35
}
36

37
async function asyncWriteFile(path: string, contents: string): Promise<void> {
NEW
38
  return new Promise<void>((resolve) => {
×
NEW
39
    writeFile(path, contents, () => {
×
NEW
40
      resolve();
×
41
    });
42
  });
43
}
44

45
let outputOptions: OutputOptions = {};
12✔
46

47
function renderStart(outputOptionsValue: OutputOptions): void {
NEW
48
  outputOptions = outputOptionsValue;
×
49
}
50

51
async function extractCSPValuesFromHTMLFile(
52
  fileName: string,
53
): Promise<Array<string>> {
NEW
54
  let fileContents = await asyncReadFile(fileName);
×
NEW
55
  if (fileContents === null) {
×
NEW
56
    return [];
×
57
  }
NEW
58
  const dom = parseDocument(fileContents, {
×
59
    withStartIndices: true,
60
    withEndIndices: true,
61
  });
NEW
62
  const cspMetaElems = findAll(
×
63
    (elem) =>
NEW
64
      elem.type === ElementType.Tag &&
×
65
      elem.name === "meta" &&
66
      elem.attribs["http-equiv"] === "content-security-policy",
67
    dom.children,
68
  );
NEW
69
  const cspValues = cspMetaElems.map((elem) => elem.attribs.content);
×
NEW
70
  for (const cspMetaElem of cspMetaElems) {
×
NEW
71
    fileContents =
×
72
      fileContents.substring(0, cspMetaElem.startIndex!) +
73
      fileContents.substring(cspMetaElem.endIndex! + 1);
74
  }
NEW
75
  await asyncWriteFile(fileName, fileContents);
×
NEW
76
  return cspValues;
×
77
}
78

79
async function writeCSPValuesToHtaccessFile(
80
  cspValues: Array<string>,
81
  options: ExtractMetaCSPEnabledOptions,
82
  htaccessFileName: string,
83
): Promise<void> {
84
  const path =
NEW
85
    options.htaccessFile ?? join(outputOptions.dir ?? "", htaccessFileName);
×
NEW
86
  let fileContents = await asyncReadFile(path);
×
NEW
87
  if (fileContents === null) {
×
NEW
88
    throw new Error('Could not read htaccess file at path "' + path + '".');
×
89
  }
NEW
90
  fileContents +=
×
91
    cspValues
92
      .map(
93
        (value) =>
NEW
94
          'Header always set Content-Security-Policy "' +
×
95
          escapeValue(value) +
96
          '"',
97
      )
98
      .join("\n") + "\n";
NEW
99
  await asyncWriteFile(path, fileContents);
×
100
}
101

102
function closeBundle(
103
  options: ExtractMetaCSPEnabledOptions,
104
  htaccessFileName: string,
105
): PluginHooks["closeBundle"] {
NEW
106
  return {
×
107
    order: "post",
108
    sequential: true,
109
    handler: async (): Promise<void> => {
NEW
110
      const cspValues = (
×
111
        await Promise.all(
NEW
112
          options.files.map(async (file) => extractCSPValuesFromHTMLFile(file)),
×
113
        )
114
      ).flat();
NEW
115
      await writeCSPValuesToHtaccessFile(cspValues, options, htaccessFileName);
×
116
    },
117
  };
118
}
119

120
export function extractMetaCSP(options: Options): Partial<PluginHooks> {
121
  if (!options.extractMetaCSP.enabled) {
1,566✔
122
    return {};
1,566✔
123
  }
NEW
124
  return {
×
125
    renderStart,
126
    closeBundle: closeBundle(options.extractMetaCSP, options.fileName),
127
  };
128
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc