Comparing version 1.10.1 to 1.10.3
@@ -8,2 +8,14 @@ # Change Log | ||
## [1.10.3] - 2024-06-09 | ||
### Fixed | ||
- Leave literal template unchanged if no var is found with this name. | ||
## [1.10.1] - 2024-06-03 | ||
### Fixed | ||
- Regression when using a var name in VarRegexReplacer. | ||
## [1.10.0] - 2024-06-03 | ||
@@ -10,0 +22,0 @@ |
import { SsgContext } from "./SsgContext.js"; | ||
import { SsgFile } from "./util"; | ||
export type OutputFunc = (context: SsgContext, outputFile: SsgFile) => Promise<void>; | ||
import { FileContents } from "./util"; | ||
export type OutputFunc = (context: SsgContext, outputFile: FileContents) => Promise<void>; |
/// <reference types="node" /> | ||
import { Logger } from "./Logger.js"; | ||
import { SsgFile } from "./util"; | ||
import { FileContents } from "./util"; | ||
export interface SsgContext<V = any> extends Logger { | ||
@@ -22,3 +22,3 @@ /** | ||
*/ | ||
file: SsgFile; | ||
file: FileContents; | ||
getVar(varName: string): string | undefined; | ||
@@ -43,3 +43,3 @@ setVar(varName: string, value: any): void; | ||
*/ | ||
read(filePath: string): SsgFile; | ||
read(filePath: string): FileContents; | ||
/** | ||
@@ -52,3 +52,3 @@ * Reads a file and assign it to the context's `file`. | ||
*/ | ||
newOutput(filePath: string, encoding?: BufferEncoding): SsgFile; | ||
newOutput(filePath: string, encoding?: BufferEncoding): FileContents; | ||
} |
/// <reference types="node" /> | ||
import { SsgContext } from "./SsgContext.js"; | ||
import { Logger } from "./Logger.js"; | ||
import { SsgFile } from "./util"; | ||
import { FileContents } from "./util"; | ||
export declare class SsgContextImpl<V = any> implements SsgContext<V> { | ||
@@ -28,9 +28,9 @@ readonly locale: string; | ||
protected stack: string[]; | ||
constructor(locale: string, vars?: Map<string, any>, name?: string, logger?: Logger, currentFile?: SsgFile | undefined); | ||
constructor(locale: string, vars?: Map<string, any>, name?: string, logger?: Logger, currentFile?: FileContents | undefined); | ||
protected _name: string; | ||
get name(): string; | ||
set name(newName: string); | ||
protected _file: SsgFile | undefined; | ||
get file(): SsgFile; | ||
set file(value: SsgFile); | ||
protected _file: FileContents | undefined; | ||
get file(): FileContents; | ||
set file(value: FileContents); | ||
getVar(varName: string): string | undefined; | ||
@@ -41,4 +41,4 @@ setVar(varName: string, value: any): void; | ||
pop(): SsgContext; | ||
read(filePath: string): SsgFile; | ||
newOutput(filePath: string, encoding?: BufferEncoding): SsgFile; | ||
read(filePath: string): FileContents; | ||
newOutput(filePath: string, encoding?: BufferEncoding): FileContents; | ||
} |
import { ObjectUtil } from "./util/ObjectUtil.js"; | ||
import { ConsoleLogger } from "./ConsoleLogger"; | ||
import { HtmlSsgFile, SsgFile } from "./util"; | ||
import { FileContents, HtmlSsgFile } from "./util"; | ||
import * as assert from "node:assert"; | ||
@@ -84,4 +84,4 @@ export class SsgContextImpl { | ||
this.file = filePath.endsWith(".html") | ||
? HtmlSsgFile.read(this, filePath) | ||
: SsgFile.read(this, filePath); | ||
? HtmlSsgFile.read(filePath) | ||
: FileContents.read(filePath); | ||
return this.file; | ||
@@ -95,3 +95,3 @@ } | ||
try { | ||
lang = SsgFile.getLang(this, filePath); | ||
lang = FileContents.getLang(filePath); | ||
} | ||
@@ -106,7 +106,7 @@ catch (e) { | ||
if (filePath.endsWith(".html")) { | ||
const fileInfo = new SsgFile(filePath, encoding, this.file.contents, creationDate, lang); | ||
const fileInfo = new FileContents(filePath, encoding, this.file.contents, creationDate, lang); | ||
outFile = HtmlSsgFile.create(fileInfo); | ||
} | ||
else { | ||
outFile = new SsgFile(filePath, encoding, this.file.contents, creationDate, lang); | ||
outFile = new FileContents(filePath, encoding, this.file.contents, creationDate, lang); | ||
} | ||
@@ -113,0 +113,0 @@ this.logger.debug("Created new output file", outFile.name); |
import path from "path"; | ||
import { RegexReplaceCommand } from "../../RegexReplaceCommand.js"; | ||
import { SsgFile } from "../../../../../util/index.js"; | ||
import { FileContents } from "../../../../../util"; | ||
/** | ||
@@ -27,3 +27,3 @@ * Replaces SSI's `<!-- #include virtual="myFileName" -->` by fileName's contents. | ||
const fileName = path.join(currentDir, toInclude); | ||
const replacement = SsgFile.read(context, fileName); | ||
const replacement = FileContents.read(fileName); | ||
return replacement.contents; | ||
@@ -30,0 +30,0 @@ } |
@@ -10,3 +10,3 @@ import { RegexReplacer } from "../RegexReplacer.js"; | ||
constructor(context: C, varName?: string, defaultHandlers?: StringContextHandler<C>[]); | ||
replace(_match: string, ...args: any[]): string; | ||
replace(match: string, ...args: any[]): string; | ||
} |
@@ -7,3 +7,3 @@ export class VarRegexReplacer { | ||
} | ||
replace(_match, ...args) { | ||
replace(match, ...args) { | ||
let varStr = this.context.getVar(this.varName === VarRegexReplacer.REGEXP_DEFAULT ? args[0] : this.varName); | ||
@@ -13,5 +13,5 @@ if (!varStr) { | ||
} | ||
return varStr || ""; | ||
return varStr || match; | ||
} | ||
} | ||
VarRegexReplacer.REGEXP_DEFAULT = "(.*?)"; |
@@ -16,3 +16,3 @@ import * as process from "process"; | ||
context.log("Copying to", dest, copies); | ||
const copiedFiles = await FileUtil.ssgCopy(dest, copies, this.config.options); | ||
const copiedFiles = await FileUtil.copy(dest, copies, this.config.options); | ||
const cwd = process.cwd(); | ||
@@ -19,0 +19,0 @@ const files = copiedFiles.map(file => file.startsWith(cwd) ? file.substring(cwd.length + 1) : file); |
import { SsgStep } from "./SsgStep.js"; | ||
import { SsgContext } from "../SsgContext.js"; | ||
import { SsgConfig } from "../SsgConfig"; | ||
import { SsgFile } from "../util"; | ||
import { FileContents } from "../util"; | ||
export interface DirectoryStepConfig extends SsgConfig { | ||
@@ -50,5 +50,3 @@ /** | ||
*/ | ||
protected abstract processDirs(context: SsgContext, dirNames: string[], outputFile: SsgFile): Promise<void>; | ||
protected findDirs(fromDirs: string[]): Promise<string[]>; | ||
protected findSubDirs(ofDir: string): Promise<string[]>; | ||
protected abstract processDirs(context: SsgContext, dirNames: string[], outputFile: FileContents): Promise<void>; | ||
} |
import { FileUtil } from "../util"; | ||
import path from "path"; | ||
/** | ||
@@ -29,35 +28,6 @@ * A step to enrich a template from some subdirectories processing. | ||
const outputFile = context.newOutput(outputFilePath); | ||
const dirNames = (await this.findDirs(this.config.rootDirs)) | ||
.filter(dirName => !this.config.excludedDirs.includes(dirName)); | ||
const dirNames = await FileUtil.findDirs(this.config.rootDirs, this.config.excludedDirs); | ||
await this.processDirs(context, dirNames, outputFile); | ||
return { directoryCount: dirNames.length }; | ||
} | ||
async findDirs(fromDirs) { | ||
let dirNames = []; | ||
for (let fromDir of fromDirs) { | ||
const subDirs = await this.findSubDirs(fromDir); | ||
dirNames = dirNames.concat(subDirs); | ||
} | ||
return dirNames; | ||
} | ||
async findSubDirs(ofDir) { | ||
let subDirs = []; | ||
if (ofDir.endsWith("/*/")) { | ||
const baseDir = ofDir.substring(0, ofDir.length - 3); | ||
if (baseDir.endsWith("/*")) { | ||
const dirs = (await this.findDirs([baseDir + "/"])) | ||
.filter(dirName => !this.config.excludedDirs.includes(dirName)); | ||
for (const dir of dirs) { | ||
subDirs = subDirs.concat(await this.findDirs([dir + "/*/"])); | ||
} | ||
} | ||
else { | ||
subDirs = (await FileUtil.dirNames(baseDir)).map(x => path.join(baseDir, x)); | ||
} | ||
} | ||
else { | ||
subDirs = [ofDir]; | ||
} | ||
return subDirs; | ||
} | ||
} |
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
import { IOptions } from "glob"; | ||
import { Dirent } from "node:fs"; | ||
/** | ||
* File utility functions | ||
*/ | ||
export declare class FileUtil { | ||
/** | ||
* Converts encoding names to Node's buffer encoding names. | ||
* | ||
* @param encoding The encoding name ("iso-8859-1", "windows-1252", etc.) | ||
* @return The matching BufferEncoding, or undefined if not supported. | ||
*/ | ||
static toBufferEncoding(encoding: string | undefined): BufferEncoding | undefined; | ||
/** | ||
* Detect the encoding of some file contents. | ||
* | ||
* @param fileName The name of the file to read. | ||
*/ | ||
static detectEncoding(fileName: string): BufferEncoding | undefined; | ||
static getCharSet(html: HTMLElement): BufferEncoding | undefined; | ||
/** | ||
* Detect the encoding of some contents. | ||
* | ||
* @param buffer The buffer holding the contents. | ||
*/ | ||
static detectContentsEncoding(buffer: Buffer): BufferEncoding | undefined; | ||
/** | ||
* Checks if a directory exists and, if not, creates it. | ||
* | ||
* @param dir The path of the directory that must exist. | ||
* @param filePath The path of the directory that must exist. | ||
*/ | ||
static ensureDirectoryExistence(dir: string): string; | ||
static writeFile(fileName: string, contents: string, encoding: BufferEncoding): Promise<void>; | ||
static dirNames(dir: string): Promise<string[]>; | ||
static ensureDirectoryOf(filePath: string): string; | ||
/** | ||
* Writes a file. If the file directory doesn't exit, it is created. | ||
* | ||
* @param filePath The path of the file to write. | ||
* @param contents The file contents to write. | ||
* @param encoding The file contents encoding scheme. | ||
*/ | ||
static writeFile(filePath: string, contents: string, encoding: BufferEncoding): Promise<void>; | ||
/** | ||
* Get a list of subdirectories' names. | ||
* | ||
* @param fromDir The name of the root directory to look from. | ||
*/ | ||
static subDirs(fromDir: string): Promise<Dirent[]>; | ||
/** | ||
* Get a list of subdirectories' names. | ||
* | ||
* @param fromDir The name of the root directory to look from. | ||
*/ | ||
static subDirsNames(fromDir: string): Promise<string[]>; | ||
static findDirs(fromDirs: string[], excludedDirs?: string[]): Promise<string[]>; | ||
static findSubDirs(ofDir: string, excludedDirs?: string[]): Promise<string[]>; | ||
/** | ||
* Copy files to a destination directory. | ||
@@ -23,6 +64,5 @@ * | ||
*/ | ||
static ssgCopy(toDir: string, sourcePatterns: string[], options?: IOptions): Promise<string[]>; | ||
static copy(toDir: string, sourcePatterns: string[], options?: IOptions): Promise<string[]>; | ||
static copyFiles(sourceFiles: string[], toDir: string): string[]; | ||
static copyFile(sourceFile: string, toDir: string): string; | ||
static getContentType(html: HTMLElement): BufferEncoding | undefined; | ||
} |
import * as fs from "fs"; | ||
import { promises as fsAsync } from "fs"; | ||
import { detectEncoding } from "char-encoding-detector"; | ||
import { detectEncoding as _detectEncoding } from "char-encoding-detector"; | ||
import path from "path"; | ||
import { readdir } from "fs/promises"; | ||
import { promise as glob } from "glob-promise"; | ||
/** | ||
* File utility functions | ||
*/ | ||
export class FileUtil { | ||
/** | ||
* Converts encoding names to Node's buffer encoding names. | ||
* | ||
* @param encoding The encoding name ("iso-8859-1", "windows-1252", etc.) | ||
* @return The matching BufferEncoding, or undefined if not supported. | ||
*/ | ||
static toBufferEncoding(encoding) { | ||
@@ -22,7 +31,20 @@ switch (encoding === null || encoding === void 0 ? void 0 : encoding.toLowerCase()) { | ||
} | ||
/** | ||
* Detect the encoding of some file contents. | ||
* | ||
* @param fileName The name of the file to read. | ||
*/ | ||
static detectEncoding(fileName) { | ||
const fileBuffer = fs.readFileSync(fileName); | ||
return FileUtil.detectContentsEncoding(fileBuffer); | ||
} | ||
/** | ||
* Detect the encoding of some contents. | ||
* | ||
* @param buffer The buffer holding the contents. | ||
*/ | ||
static detectContentsEncoding(buffer) { | ||
let guessedEncoding = undefined; | ||
try { | ||
guessedEncoding = detectEncoding(fileBuffer); | ||
guessedEncoding = _detectEncoding(buffer); | ||
} | ||
@@ -38,34 +60,73 @@ catch (e) { | ||
} | ||
static getCharSet(html) { | ||
let charSet; | ||
const charsetEl = html.querySelector("html[charset]"); | ||
if (charsetEl) { | ||
const charSetValue = charsetEl.getAttribute("charset") || undefined; | ||
charSet = this.toBufferEncoding(charSetValue); | ||
} | ||
return charSet; | ||
} | ||
/** | ||
* Checks if a directory exists and, if not, creates it. | ||
* | ||
* @param dir The path of the directory that must exist. | ||
* @param filePath The path of the directory that must exist. | ||
*/ | ||
static ensureDirectoryExistence(dir) { | ||
const dirname = path.dirname(dir); | ||
static ensureDirectoryOf(filePath) { | ||
const dirname = path.dirname(filePath); | ||
if (!fs.existsSync(dirname)) { | ||
this.ensureDirectoryExistence(dirname); // Recursive to create the whole directories chain. | ||
this.ensureDirectoryOf(dirname); // Recursive to create the whole directories chain. | ||
fs.mkdirSync(dirname); | ||
} | ||
return path.resolve(dir); | ||
return path.resolve(filePath); | ||
} | ||
static async writeFile(fileName, contents, encoding) { | ||
this.ensureDirectoryExistence(fileName); | ||
return fsAsync.writeFile(fileName, contents, { encoding }); | ||
/** | ||
* Writes a file. If the file directory doesn't exit, it is created. | ||
* | ||
* @param filePath The path of the file to write. | ||
* @param contents The file contents to write. | ||
* @param encoding The file contents encoding scheme. | ||
*/ | ||
static async writeFile(filePath, contents, encoding) { | ||
this.ensureDirectoryOf(filePath); | ||
return fsAsync.writeFile(filePath, contents, { encoding }); | ||
} | ||
static async dirNames(dir) { | ||
const dirs = await readdir(dir, { withFileTypes: true }); | ||
return dirs.filter(dirent => dirent.isDirectory()) | ||
.map(dirent => dirent.name); | ||
/** | ||
* Get a list of subdirectories' names. | ||
* | ||
* @param fromDir The name of the root directory to look from. | ||
*/ | ||
static async subDirs(fromDir) { | ||
const dirs = await readdir(fromDir, { withFileTypes: true }); | ||
return dirs.filter(dirEntry => dirEntry.isDirectory()); | ||
} | ||
/** | ||
* Get a list of subdirectories' names. | ||
* | ||
* @param fromDir The name of the root directory to look from. | ||
*/ | ||
static async subDirsNames(fromDir) { | ||
const subDirs = await this.subDirs(fromDir); | ||
return subDirs.map(dirEntry => dirEntry.name); | ||
} | ||
static async findDirs(fromDirs, excludedDirs = []) { | ||
let dirNames = []; | ||
for (let fromDir of fromDirs) { | ||
const subDirs = await this.findSubDirs(fromDir, excludedDirs); | ||
dirNames = dirNames.concat(subDirs); | ||
} | ||
return dirNames; | ||
} | ||
static async findSubDirs(ofDir, excludedDirs = []) { | ||
let subDirs = []; | ||
if (ofDir.endsWith("/*/")) { | ||
const baseDir = ofDir.substring(0, ofDir.length - 3); | ||
if (baseDir.endsWith("/*")) { | ||
const dirs = (await this.findDirs([baseDir + "/"])) | ||
.filter(dirName => !excludedDirs.includes(dirName)); | ||
for (const dir of dirs) { | ||
subDirs = subDirs.concat(await this.findDirs([dir + "/*/"])); | ||
} | ||
} | ||
else { | ||
subDirs = (await FileUtil.subDirsNames(baseDir)).map(x => path.join(baseDir, x)); | ||
} | ||
} | ||
else { | ||
subDirs = [ofDir]; | ||
} | ||
return subDirs; | ||
} | ||
/** | ||
* Copy files to a destination directory. | ||
@@ -78,3 +139,3 @@ * | ||
*/ | ||
static async ssgCopy(toDir, sourcePatterns, options) { | ||
static async copy(toDir, sourcePatterns, options) { | ||
let result = []; | ||
@@ -99,26 +160,6 @@ for (const sourcePattern of sourcePatterns) { | ||
const to = path.join(toDir, sourceFile); | ||
this.ensureDirectoryExistence(to); | ||
this.ensureDirectoryOf(to); | ||
fs.copyFileSync(from, to); | ||
return to; | ||
} | ||
static getContentType(html) { | ||
let contentType; | ||
const contentTypeEl = html.querySelector("meta[http-equiv='Content-Type']"); | ||
if (contentTypeEl) { | ||
const content = contentTypeEl.getAttribute("content"); | ||
if (content) { | ||
const values = content.split(";"); | ||
if (values.length > 0) { | ||
let value = values[1]; | ||
let key = "charset="; | ||
let charsetPos = value.indexOf(key); | ||
if (charsetPos >= 0) { | ||
const charset = value.substring(charsetPos + key.length).toLowerCase().trim(); | ||
contentType = this.toBufferEncoding(charset); | ||
} | ||
} | ||
} | ||
} | ||
return contentType; | ||
} | ||
} |
/// <reference types="node" /> | ||
import { SsgContext } from "../../SsgContext.js"; | ||
import { SsgFile, SsgFileLang } from "./SsgFile.js"; | ||
import { FileContents, SsgFileLang } from "./FileContents"; | ||
import { JSDOM } from "jsdom"; | ||
@@ -36,3 +35,3 @@ export type HtmlMeta = { | ||
*/ | ||
export declare class HtmlSsgFile extends SsgFile { | ||
export declare class HtmlSsgFile extends FileContents { | ||
readonly meta: HtmlMeta; | ||
@@ -52,4 +51,9 @@ readonly links: HtmlLinks; | ||
set contents(value: string); | ||
static read(context: SsgContext, fileName: string): HtmlSsgFile; | ||
static create(fileInfo: SsgFile): HtmlSsgFile; | ||
static read(fileName: string): HtmlSsgFile; | ||
/** | ||
* Create an HtmlSsgFile from a SsgFile | ||
* | ||
* @param fileInfo | ||
*/ | ||
static create(fileInfo: FileContents): HtmlSsgFile; | ||
static getMeta(name: string, doc: Document): string[]; | ||
@@ -56,0 +60,0 @@ static getLink(rel: LinkType, doc: Document): Link | undefined; |
@@ -1,3 +0,4 @@ | ||
import { SsgFile } from "./SsgFile.js"; | ||
import { FileContents } from "./FileContents"; | ||
import { JSDOM } from "jsdom"; | ||
import { HtmlUtil } from "./HtmlUtil"; | ||
export var LinkType; | ||
@@ -17,3 +18,3 @@ (function (LinkType) { | ||
*/ | ||
export class HtmlSsgFile extends SsgFile { | ||
export class HtmlSsgFile extends FileContents { | ||
constructor(name, encoding, contents, lastModified, lang, meta, links, title) { | ||
@@ -48,8 +49,17 @@ super(name, encoding, contents, lastModified, lang); | ||
} | ||
static read(context, fileName) { | ||
const fileInfo = super.read(context, fileName); | ||
static read(fileName) { | ||
const fileInfo = super.read(fileName); | ||
return this.create(fileInfo); | ||
} | ||
/** | ||
* Create an HtmlSsgFile from a SsgFile | ||
* | ||
* @param fileInfo | ||
*/ | ||
static create(fileInfo) { | ||
const dom = new JSDOM(fileInfo.contents); | ||
const declaredEncoding = HtmlUtil.getHtmlDeclaredEncoding(dom); | ||
if (declaredEncoding !== fileInfo.encoding) { | ||
console.warn(`Encoding of ${fileInfo.name} is ${fileInfo.encoding} but declares ${declaredEncoding}`); | ||
} | ||
let title; | ||
@@ -56,0 +66,0 @@ const doc = dom.window.document; |
@@ -1,3 +0,4 @@ | ||
export * from "./SsgFile.js"; | ||
export * from "./FileContents.js"; | ||
export * from "./FileUtil.js"; | ||
export * from "./HtmlSsgFile.js"; | ||
export * from "./HtmlUtil.js"; |
@@ -1,3 +0,4 @@ | ||
export * from "./SsgFile.js"; | ||
export * from "./FileContents.js"; | ||
export * from "./FileUtil.js"; | ||
export * from "./HtmlSsgFile.js"; | ||
export * from "./HtmlUtil.js"; |
@@ -5,3 +5,3 @@ { | ||
"author": "Jérôme Beau <javarome@gmail.com> (https://javarome.com)", | ||
"version": "1.10.1", | ||
"version": "1.10.3", | ||
"description": "Static Site Generation TypeScript API", | ||
@@ -20,5 +20,5 @@ "exports": "./dist/src/index.js", | ||
"scripts": { | ||
"prebuild": "npm install", | ||
"prebuild": "npm install && npm test", | ||
"build": "rm -Rf dist && tsc --project tsconfig.prod.json", | ||
"prepublishOnly": "npm run build && npm test", | ||
"prepublishOnly": "npm run build", | ||
"test": "rm -Rf out && tsx src/testAll.ts", | ||
@@ -25,0 +25,0 @@ "test-one": "rm -Rf out && tsx src/step/content/replace/html/StringEchoVarReplaceCommandTest.ts", |
@@ -20,6 +20,3 @@ { | ||
"src" | ||
], | ||
"exclude": [ | ||
"test/**/*.*" | ||
] | ||
} |
@@ -6,3 +6,6 @@ { | ||
"sourceMap": false | ||
} | ||
}, | ||
"exclude": [ | ||
"test/**/*.*" | ||
] | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
138624
131
2184