|
@@ -40,27 +40,104 @@ const PUG_COMPILE_ARGUMENTS = {
|
|
|
module: true,
|
|
module: true,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * @param {string} compiled
|
|
|
|
|
+ * @return {{duplicates: Array<string>, found: Array<string>}}
|
|
|
|
|
+ * @private
|
|
|
|
|
+ */
|
|
|
|
|
+function _findAllMetaTags(compiled) {
|
|
|
|
|
+ const inString = /name=(\\?["'`])(ol-.+?)\1/g
|
|
|
|
|
+ const asExpression = /pug\.attr\("name",\s*(["'`])(ol-.+?)\1/g
|
|
|
|
|
+
|
|
|
|
|
+ const found = new Set()
|
|
|
|
|
+ const duplicates = new Set()
|
|
|
|
|
+ for (const regex of [inString, asExpression]) {
|
|
|
|
|
+ for (const [, , name] of compiled.matchAll(regex)) {
|
|
|
|
|
+ if (found.has(name)) duplicates.add(name)
|
|
|
|
|
+ found.add(name)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // Special case: Ignore the loop for adding permissions meta tags.
|
|
|
|
|
+ duplicates.delete('ol-cannot-')
|
|
|
|
|
+ return { found: Array.from(found), duplicates: Array.from(duplicates) }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @param {string} filePath
|
|
|
|
|
+ * @param {string} firstLine
|
|
|
|
|
+ * @return {boolean}
|
|
|
|
|
+ * @private
|
|
|
|
|
+ */
|
|
|
|
|
+function _expectMetaFor(filePath, firstLine) {
|
|
|
|
|
+ // no-js pages have no use for ol-meta tags
|
|
|
|
|
+ if (firstLine.match(/extends .*layout\/layout-no-js/)) return false
|
|
|
|
|
+ // plain html pages have no use for ol-meta tags
|
|
|
|
|
+ if (firstLine === 'doctype html') return false
|
|
|
|
|
+ // xml pages do not use meta tags
|
|
|
|
|
+ if (firstLine === 'doctype xml') return false
|
|
|
|
|
+ // view includes should not add meta tags as we cannot trace these easily.
|
|
|
|
|
+ if (Object.values(Settings.viewIncludes).flat().includes(filePath)) {
|
|
|
|
|
+ if (
|
|
|
|
|
+ filePath === Path.resolve('modules/writefull/app/views/_editor_meta.pug')
|
|
|
|
|
+ ) {
|
|
|
|
|
+ // Special case: The Writefull module adds meta tags to editor, see inline comment there
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ // default case: no meta tags
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ // default to expect meta tags in top-level templates
|
|
|
|
|
+ return true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @param {string} filePath
|
|
|
|
|
+ * @param {string} compiled
|
|
|
|
|
+ */
|
|
|
|
|
+function checkForDuplicateMeta(filePath, compiled) {
|
|
|
|
|
+ const { found, duplicates } = _findAllMetaTags(compiled)
|
|
|
|
|
+
|
|
|
|
|
+ if (duplicates.length !== 0) {
|
|
|
|
|
+ throw new Error(
|
|
|
|
|
+ `Found duplicate meta tags in ${filePath} (or it's imports): ${Array.from(duplicates)}`
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ const firstLine = fs.readFileSync(filePath, 'utf-8').split('\n', 1)[0]
|
|
|
|
|
+ const expectNoMeta = _expectMetaFor(filePath, firstLine)
|
|
|
|
|
+ if (found.length === 0 && expectNoMeta) {
|
|
|
|
|
+ throw new Error(
|
|
|
|
|
+ `Expected to find meta entries in ${filePath} (or it's imports)`
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!expectNoMeta && found.length !== 0) {
|
|
|
|
|
+ throw new Error(
|
|
|
|
|
+ `Expected to find no meta entries in plain html or 'viewIncludes'. Found ${Array.from(found)} in ${filePath} (or it's imports)`
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function precompileViewsAndCacheToDisk() {
|
|
function precompileViewsAndCacheToDisk() {
|
|
|
const startTime = Date.now()
|
|
const startTime = Date.now()
|
|
|
let success = 0
|
|
let success = 0
|
|
|
let precompiled = 0
|
|
let precompiled = 0
|
|
|
- for (const filename of buildViewList()) {
|
|
|
|
|
- const precompiledFilename = filename.replace(/\.pug$/, '.js')
|
|
|
|
|
|
|
+ for (const filePath of buildViewList()) {
|
|
|
|
|
+ const precompiledFilename = filePath.replace(/\.pug$/, '.js')
|
|
|
try {
|
|
try {
|
|
|
- const src = pug.compileFileClient(filename, PUG_COMPILE_ARGUMENTS)
|
|
|
|
|
|
|
+ const compiled = pug.compileFileClient(filePath, PUG_COMPILE_ARGUMENTS)
|
|
|
try {
|
|
try {
|
|
|
- if (fs.readFileSync(precompiledFilename, 'utf-8') === src) {
|
|
|
|
|
|
|
+ if (fs.readFileSync(precompiledFilename, 'utf-8') === compiled) {
|
|
|
precompiled++
|
|
precompiled++
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
} catch {}
|
|
} catch {}
|
|
|
- fs.writeFileSync(precompiledFilename, src, {
|
|
|
|
|
|
|
+ checkForDuplicateMeta(filePath, compiled)
|
|
|
|
|
+ fs.writeFileSync(precompiledFilename, compiled, {
|
|
|
encoding: 'utf-8',
|
|
encoding: 'utf-8',
|
|
|
mode: 0o644,
|
|
mode: 0o644,
|
|
|
})
|
|
})
|
|
|
success++
|
|
success++
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- logger.err({ err, filename }, 'failed to precompile pug template')
|
|
|
|
|
|
|
+ logger.err({ err, filePath }, 'failed to precompile pug template')
|
|
|
throw err
|
|
throw err
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -71,6 +148,11 @@ function precompileViewsAndCacheToDisk() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
module.exports = {
|
|
|
|
|
+ // for tests
|
|
|
|
|
+ PUG_COMPILE_ARGUMENTS,
|
|
|
|
|
+ _expectMetaFor,
|
|
|
|
|
+ _findAllMetaTags,
|
|
|
|
|
+
|
|
|
compileViewIncludes(app) {
|
|
compileViewIncludes(app) {
|
|
|
const viewIncludes = {}
|
|
const viewIncludes = {}
|
|
|
for (const [view, paths] of Object.entries(Settings.viewIncludes)) {
|
|
for (const [view, paths] of Object.entries(Settings.viewIncludes)) {
|
|
@@ -92,28 +174,28 @@ module.exports = {
|
|
|
let success = 0
|
|
let success = 0
|
|
|
let precompiled = 0
|
|
let precompiled = 0
|
|
|
let failures = 0
|
|
let failures = 0
|
|
|
- for (const filename of buildViewList()) {
|
|
|
|
|
- const precompiledFilename = filename.replace(/\.pug$/, '.js')
|
|
|
|
|
|
|
+ for (const filePath of buildViewList()) {
|
|
|
|
|
+ const precompiledFilename = filePath.replace(/\.pug$/, '.js')
|
|
|
if (fs.existsSync(precompiledFilename)) {
|
|
if (fs.existsSync(precompiledFilename)) {
|
|
|
- logger.debug({ filename }, 'loading precompiled pug template')
|
|
|
|
|
|
|
+ logger.debug({ filePath }, 'loading precompiled pug template')
|
|
|
try {
|
|
try {
|
|
|
- pug.cache[filename] = require(precompiledFilename)
|
|
|
|
|
|
|
+ pug.cache[filePath] = require(precompiledFilename)
|
|
|
precompiled++
|
|
precompiled++
|
|
|
continue
|
|
continue
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
logger.error(
|
|
logger.error(
|
|
|
- { filename, err },
|
|
|
|
|
|
|
+ { filePath, err },
|
|
|
'error loading precompiled pug template'
|
|
'error loading precompiled pug template'
|
|
|
)
|
|
)
|
|
|
failures++
|
|
failures++
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
try {
|
|
try {
|
|
|
- logger.warn({ filename }, 'compiling pug template at boot time')
|
|
|
|
|
- pug.compileFile(filename, PUG_COMPILE_ARGUMENTS)
|
|
|
|
|
|
|
+ logger.warn({ filePath }, 'compiling pug template at boot time')
|
|
|
|
|
+ pug.compileFile(filePath, PUG_COMPILE_ARGUMENTS)
|
|
|
success++
|
|
success++
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- logger.error({ filename, err }, 'error compiling pug template')
|
|
|
|
|
|
|
+ logger.error({ filePath, err }, 'error compiling pug template')
|
|
|
failures++
|
|
failures++
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|