Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"type-check": "npx tsc --noEmit",
"update-pkg": "npm upgrade -S",
"rm": "rmdir /s /q lib",
"test:build": "npx esbuild ./src/**/* --format=cjs --sourcemap --outdir=lib --platform=node",
"test:build": "npx esbuild ./src/**/* ./src/*.ts --format=cjs --sourcemap --outdir=lib --platform=node",
"test": "npm run test:build && node --test",
"build:build-ci": "npx esbuild ./src/index.ts --format=cjs --outdir=lib --bundle --external:tree-sitter --external:tree-sitter-typescript --external:tree-sitter-php --external:tree-sitter-javascript --external:@babel/preset-typescript --platform=node",
"test:ci": "npm run build:build-ci && npm run test",
Expand Down Expand Up @@ -84,4 +84,4 @@
"esbuild": "0.25.9",
"typescript": "^5.9.3"
}
}
}
11 changes: 7 additions & 4 deletions src/extractors/auditStrings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,21 @@ export function audit(args: Args, translationsUnion: SetOfBlocks) {
console.log("\nAudit Complete!");
if (auditor.getResults().length === 0) {
console.log("No issues found! 🎉");
//if there are no errors, we can remove the audit.log file
// if there are no errors, we can remove the audit.log file
try {
unlinkSync(path.join(args.paths.cwd, "audit.log"));
} catch (_error) {
//ignore
}
} else {
console.log(`Found ${auditor.getResults().length} issues!`);
const results = auditor.getResults().join("\n");
console.log(results);
// Print the results if not in silent mode
if (args.options?.silent !== true) {
const results = auditor.getResults().join("\n");
console.log(results);
}
/** Write the audit results to a file */
writeFileSync(path.join(args.paths.cwd, "audit.log"), results);
writeFileSync(path.join(args.paths.cwd, "audit.log"), auditor.getResults().join("\n"));
}
}

Expand Down
101 changes: 66 additions & 35 deletions src/extractors/headers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import path from 'node:path'
import process from 'node:process'
import * as readline from 'node:readline'
import { SetOfBlocks } from 'gettext-merger'
import { modulePath } from '../const.js'
import { getEncodingCharset } from '../fs/fs.js'
Expand All @@ -12,11 +14,13 @@ import { extractPhpPluginData } from './php.js'
* Checks if required fields are missing and logs a clear error message
* @param {object} headerData - The header data to validate
* @param {boolean} debug - Debug mode flag
* @param {boolean} silent - Silent mode flag
* @returns {boolean} - true if all required fields are present, false otherwise
*/
function validateRequiredFields(
headerData: I18nHeaders,
debug: boolean,
silent = false,
): boolean {
// Define the required fields with strict key types
const requiredFields: {
Expand All @@ -40,39 +44,41 @@ function validateRequiredFields(
);

if (missingFields.length > 0) {
console.error("\n! Missing required information for POT file header:\n");
if (!silent) {
console.error("\n! Missing required information for POT file header:\n");

for (const field of missingFields) {
console.error(
` - ${field.name} is missing or has a default value (eg. version: 0.0.1")`,
);
}

console.error(
"\nPlease provide this information adding the missing fields inside the headers object of the plugin/theme declaration or to the package.json file.",
"\nFor more information check the documentation at https://github.com/wp-blocks/makePot",
);
for (const field of missingFields) {
console.error(
` - ${field.name} is missing or has a default value (eg. version: 0.0.1")`,
);
}

if (missingFields.some((field) => field.key === "email")) {
console.error(
"\n\nWordpress didn't require an email field in the headers object but it's required in order to generate a valid pot file.",
'\nPlease add the email field to the package.json file (author field eg. author: "AUTHOR <EMAIL>")',
'\nor inject those information using the --headers flag to the "makePot" command (eg. --headers=email:erik@ck.it).',
"\nPlease provide this information adding the missing fields inside the headers object of the plugin/theme declaration or to the package.json file.",
"\nFor more information check the documentation at https://github.com/wp-blocks/makePot",
);
}

if (missingFields && debug) {
console.error(
"\nDebug information:",
"\nMissing fields:",
missingFields,
"\nHeader data:",
headerData,
);
}
if (missingFields.some((field) => field.key === "email")) {
console.error(
"\n\nWordpress didn't require an email field in the headers object but it's required in order to generate a valid pot file.",
'\nPlease add the email field to the package.json file (author field eg. author: "AUTHOR <EMAIL>")',
'\nor inject those information using the --headers flag to the "makePot" command (eg. --headers=email:erik@ck.it).',
"\nFor more information check the documentation at https://github.com/wp-blocks/makePot",
);
}

console.error("\n");
if (missingFields && debug) {
console.error(
"\nDebug information:",
"\nMissing fields:",
missingFields,
"\nHeader data:",
headerData,
);
}

console.error("\n");
}

return false;
}
Expand Down Expand Up @@ -153,10 +159,7 @@ export function getAuthorFromPackage(
} else if (Array.isArray(value)) {
for (const author of value) {
if (!author) continue;
if (
typeof author === "string" ||
(typeof author === "object")
) {
if (typeof author === "string" || typeof author === "object") {
authorData = extractAuthorData(author as string | AuthorData);
if (authorData) break;
}
Expand Down Expand Up @@ -196,7 +199,9 @@ function consolidateUserHeaderData(args: Args): I18nHeaders {
"maintainers",
);
// get author data from package.json
const pkgAuthor = getAuthorFromPackage(pkgJsonData as unknown as Record<string, unknown>);
const pkgAuthor = getAuthorFromPackage(
pkgJsonData as unknown as Record<string, unknown>,
);

// get the current directory name as slug
const currentDir = path
Expand All @@ -215,7 +220,8 @@ function consolidateUserHeaderData(args: Args): I18nHeaders {
args.headers?.name?.toString().replace(/ /g, "-") ||
(args.domain === "theme" ? "THEME NAME" : "PLUGIN NAME");

const bugs = `https://wordpress.org/support/${args.domain === "theme" ? "themes" : "plugins"}/${slug}`;
const bugs = `https://wordpress.org/support/${args.domain === "theme" ? "themes" : "plugins"
}/${slug}`;

return {
...args.headers,
Expand All @@ -226,7 +232,8 @@ function consolidateUserHeaderData(args: Args): I18nHeaders {
email,
bugs,
license: args.headers?.license || "gpl-2.0 or later",
version: args.headers?.version || (pkgJsonData.version as string) || "0.0.1",
version:
args.headers?.version || (pkgJsonData.version as string) || "0.0.1",
language: "en",
xDomain: args.headers?.textDomain?.toString() || slug,
};
Expand Down Expand Up @@ -257,8 +264,32 @@ export async function generateHeader(
const { name, version } = getPkgJsonData(modulePath, "name", "version");

// Validate required fields - exit early if validation fails
if (!validateRequiredFields(headerData, args.debug)) {
process.exit(1); // Exit with error code
if (!validateRequiredFields(headerData, args.debug, args.options?.silent)) {
if (args.options?.silent) {
// In silent mode, we use defaults without asking
} else {
// Ask the user if default values should be used
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

const answer = await new Promise((resolve) => {
rl.question(
"\nMissing required fields. Use default values? (y/N) ",
resolve,
);
});
rl.close();

if (
typeof answer === "string" &&
answer.toLowerCase() !== "y" &&
answer.toLowerCase() !== "yes"
) {
process.exit(1); // Exit with error code
}
}
}

return {
Expand Down
23 changes: 13 additions & 10 deletions src/parser/taskRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,22 @@ export async function taskRunner(
})
.then((consolidated) => {
/** Log the results */
if (args.options?.silent !== true) {
for (const result of consolidated) {
if (result.blocks.length > 0) {
/**
* Add the strings to the destination set
*/
destination.addArray(result.blocks);
const strings = result.blocks.map((b) => b.msgid);
/* Log the results */
for (const result of consolidated) {
if (result.blocks.length > 0) {
/**
* Add the strings to the destination set
*/
destination.addArray(result.blocks);
const strings = result.blocks.map((b) => b.msgid);

/* Log the results */
if (args.options?.silent !== true) {
messages.push(
`✅ ${result.path} - ${strings.length} strings found [${strings.join(", ")}]`,
);
} else messages.push(`❌ ${result.path} - has no strings`);
}
} else if (args.options?.silent !== true) {
messages.push(`❌ ${result.path} - has no strings`);
}
}
})
Expand Down
27 changes: 27 additions & 0 deletions tests/generate-header.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const { describe, it } = require("node:test");
const assert = require("node:assert");
const { generateHeader } = require("../lib/extractors/headers");
const process = require("node:process");

describe("generateHeader", () => {
it("should return default headers when silent is true and fields are missing", async () => {
const args = {
slug: "test-slug",
debug: false,
domain: "plugin",
paths: { cwd: process.cwd(), out: "languages" },
options: { silent: true },
headers: {
version: "0.0.1",
author: "AUTHOR",
email: "AUTHOR EMAIL"
},
};

const headers = await generateHeader(args);

assert.ok(headers, "Headers should be generated");
assert.strictEqual(headers["Project-Id-Version"], "test-slug 0.0.1");
assert.strictEqual(headers["Last-Translator"], "AUTHOR <AUTHOR EMAIL>");
});
});