diff --git a/CHANGELOG.md b/CHANGELOG.md index fcc21a3..1b55f34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # balukajs +## 1.4.0 + +### Minor Changes + +- added -I / --input-dir to manage directory conversion. + ## 1.3.0 ### Minor Changes diff --git a/bun.lockb b/bun.lockb index faca4cd..eeefd90 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/jsconfig.json b/jsconfig.json index 32e74f1..a071476 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "module": "CommonJS", + "module": "ESNext", "target": "ES6", /* Bundler mode */ diff --git a/package.json b/package.json index 51dc964..1656da3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "balukajs", - "version": "1.3.0", + "version": "1.4.0", "main": "src/index.js", "author": { "name": "billybillydev", @@ -57,6 +57,7 @@ "change-case": "^5.4.4", "chokidar": "^3.6.0", "commander": "^12.1.0", + "dir-to-json": "^1.0.0", "jest": "^29.7.0", "tsup": "^8.0.2" } diff --git a/src/commands/convert.js b/src/commands/convert.js index 9ff367a..9f0d08a 100644 --- a/src/commands/convert.js +++ b/src/commands/convert.js @@ -1,14 +1,17 @@ import { jsonToJSDocTypes } from "$lib/json-to-jsdoc"; import { buildJsonSchema } from "$lib/json-to-schema"; import { jsonToTSInterface } from "$lib/json-to-ts-interface"; +import { organizeToJSON } from "$lib/utils"; import chalk from "chalk"; import fs from "fs"; +import { lstat } from "node:fs/promises"; import path from "path"; /** * Function responsible for converting input into formatted output * @param {Object} data - * @param {string} data.input + * @param {string=} data.input + * @param {string=} data.inputDir * @param {string=} data.output * @param {string=} data.name * @param {FormatType=} data.format @@ -16,19 +19,27 @@ import path from "path"; * @example * convert({ input: "example.json", format: "jsdoc", name: "IExample" }) */ -export function convert({ input, output, name, format }) { - if (!input) { - throw new Error("Missing -i or --input option"); +export async function convert({ input, inputDir, output, name, format }) { + const source = inputDir ?? input; + if (!source) { + throw new Error("Missing -i / --input and -I / --input-dir arguments"); } - if (!fs.existsSync(input)) { - throw new Error("No file"); + if (!fs.existsSync(source)) { + throw new Error("No file or directory"); } - - if (path.extname(input) !== ".json") { - throw new Error("File is not json"); + let data; + const stats = await lstat(source); + if (stats.isDirectory()) { + data = JSON.stringify(await organizeToJSON(source)); + } else { + if (path.extname(input) !== ".json") { + throw new Error("File is not json"); + } + data = fs.readFileSync(input, "utf-8"); + } + if (!data) { + throw new Error("Data is null or undefined"); } - - const data = fs.readFileSync(input, "utf-8"); let result = ""; const defaultName = "MyType"; diff --git a/src/commands/watch.js b/src/commands/watch.js index 5b91747..2e38e2f 100644 --- a/src/commands/watch.js +++ b/src/commands/watch.js @@ -3,15 +3,37 @@ import chokidar from "chokidar"; /** * @param {Object} data - * @param {string} data.input - * @param {string=} data.output - * @param {string=} data.name - * @param {FormatType=} data.format + * @param {string} data.input + * @param {string} data.inputDir + * @param {string=} data.output + * @param {string=} data.name + * @param {FormatType=} data.format * @returns {void} */ -export function watch({ input, output, name, format }) { - chokidar.watch(input).on("change", () => { - console.log("File changed, re-running conversion..."); - convert({ input, output, name, format }); - }); +export function watch({ input, inputDir, output, name, format }) { + if (inputDir) { + chokidar + .watch(inputDir) + .on("ready", () => { + console.log("Scan completed, running conversion..."); + convert({ inputDir, output, name, format }); + }) + .on("change", (path) => { + console.log("%s updated, re-running conversion...", path); + convert({ inputDir, output, name, format }); + }) + .on("unlink", (path) => { + console.log("File at path: % removed, re-running conversion...", path); + convert({ inputDir, output, name, format }); + }) + .on("unlinkDir", (path) => { + console.log("Directory at path: %s removed, re-running conversion...", path); + convert({ inputDir, output, name, format }); + }); + } else if (input) { + chokidar.watch(input).on("change", (path) => { + console.log("File at path: %s changed, re-running conversion...", path); + convert({ input, output, name, format }); + }); + } } diff --git a/src/index.js b/src/index.js index caec8fc..e3e8713 100755 --- a/src/index.js +++ b/src/index.js @@ -21,6 +21,7 @@ async function main() { "display the version number" ) .option("-i, --input ", "input JSON file") + .option("-I, --input-dir ", "input directory containing directories and json files") .option("-o, --output ", "output file") .option("--name ", "name of the type") .option("--format ", "output format (jsdoc, ts or schema)", "jsdoc") @@ -30,7 +31,6 @@ async function main() { if (process.argv.length <= 2) { program.help(); } - const { watch, ...restProps } = program.opts(); if (watch) { diff --git a/src/lib/utils.js b/src/lib/utils.js index 05136c0..1dd4d80 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -1,3 +1,22 @@ +import dirToJson from "dir-to-json"; +import path from "path"; +import fs from "fs"; +import { camelCase } from "change-case"; + +/** + * @typedef {object} StructureInfo + * @property {string} parent + * @property {string} path + * @property {string} name + * @property {"directory" | "file"} type + * @property {StructureInfo[]} [children] + */ + +/** + * @typedef {object} SimpleStructureInfo + * @type {Record} + */ + /** * @param {object[]} objectArray * @returns {string[]} @@ -17,3 +36,67 @@ export function detectOptionalProperties(objectArray) { return optionalProperties; } + +/** + * Takes a directory path and transform its structure to json tree object + * @param {string} directoryPath + * @returns {Object} + */ +export async function organizeToJSON(directoryPath) { + /** @type {StructureInfo} */ + const directoryObject = await dirToJson(directoryPath); + const simpleObject = await simplifyObject(directoryObject, directoryPath); + return sortObjectByKeys(simpleObject); +} + +/** + * Takes a StructureInfo object and returns a SimpleStructureInfo + * @param {StructureInfo} obj + * @param {string} rootPath + * @returns {SimpleStructureInfo} + */ +async function simplifyObject(obj, rootPath) { + const simpleObject = {}; + if (obj.type === "directory") { + for await (const child of obj.children) { + const childObject = await simplifyObject(child, rootPath); + if (childObject) { + if (child.type === "file") { + Object.entries(childObject).forEach(([k, v]) => { + simpleObject[k] = v; + }); + } else { + const name = + child.name.includes(".") || child.name.includes("-") + ? camelCase(child.name) + : child.name; + simpleObject[name] = childObject; + } + } + } + } else if (obj.type === "file") { + const filePath = path.join(rootPath, obj.path); + const isFileExists = await fs.existsSync(filePath); + const fileExtension = path.extname(filePath); + if (isFileExists) { + if (fileExtension !== ".json") { + return null; + } + const fileName = path.basename(filePath, fileExtension); + const file = await fs.readFileSync(filePath, "utf-8"); + const name = + fileName.includes(".") || fileName.includes("-") + ? camelCase(fileName) + : fileName; + simpleObject[name] = file; + } + } + return simpleObject; +} + +export function sortObjectByKeys(obj) { + // Convert object to an array of key-value pairs, sort it, and convert back to an object + return Object.fromEntries( + Object.entries(obj).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) + ); +} \ No newline at end of file diff --git a/src/types/index.js b/src/types/index.js index fa96ece..a594efd 100644 --- a/src/types/index.js +++ b/src/types/index.js @@ -1,3 +1,3 @@ /** - * @typedef {"json" | "ts" | "schema"} FormatType + * @typedef {"jsdoc" | "ts" | "schema"} FormatType */ \ No newline at end of file diff --git a/tests/convert.test.js b/tests/convert.test.js index bcf7e26..cb85983 100644 --- a/tests/convert.test.js +++ b/tests/convert.test.js @@ -1,16 +1,31 @@ import { convert } from "$commands/convert"; -import { describe, expect, it, spyOn } from "bun:test"; +import { describe, expect, it, spyOn, beforeAll } from "bun:test"; import fs from "fs"; import { access } from "fs/promises"; import path from "path"; -import { expectedJSDocOutput2 } from "tests/expected-js-doc-output"; -import { expectedTsOutput2 } from "tests/expected-ts-output"; +import { + expectedDirectoryToJSDoc, + expectedJSDocOutput2, +} from "tests/expected-js-doc-output"; import expectedSchemaOutput from "tests/expected-schema-output.json"; +import { + expectedDirectoryOverFileToTs, + expectedDirectoryToTs, + expectedTsOutput2, +} from "tests/expected-ts-output"; + +beforeAll(async () => { + const emptyDir = path.join(__dirname, "./inputs/empty-directory"); + const isDirExists = await fs.existsSync(emptyDir); + if (!isDirExists) { + await fs.mkdirSync(emptyDir); + } +}); describe("ConvertCommandNoInput", () => { - it("should throw error if input is missing", () => { + it("should throw error if input or inputDir are missing", () => { expect(() => convert({ name: "MyType" })).toThrowError( - "Missing -i or --input option" + "Missing -i / --input and -I / --input-dir arguments" ); }); }); @@ -18,10 +33,30 @@ describe("ConvertCommandNoInput", () => { describe("ConvertCommandFileNotExist", () => { it("should throw error if file does not exist", () => { const input = path.join(__dirname, "./inputs/not-existed-file.txt"); - expect(() => convert({ input, name: "MyType" })).toThrowError("No file"); + expect(() => convert({ input, name: "MyType" })).toThrowError( + "No file or directory" + ); }); }); +describe("ConvertCommandDirectoryNotExist", () => { + it("should throw error if directory does not exist", () => { + const inputDir = path.join(__dirname, "./inputs/not-existed-directory"); + expect(() => convert({ inputDir, name: "MyType" })).toThrowError( + "No file or directory" + ); + }); +}); + +// describe("ConvertCommandDataIsNullOrUndefined", () => { +// it("should throw error if data is null or undefined", () => { +// const input = path.join(__dirname, "./inputs/empty-object.json"); +// expect(() => convert({ input, name: "MyType", format: "ts" })).toThrowError( +// "Data is null or undefined" +// ); +// }); +// }); + describe("ConvertCommandNonJsonFile", () => { it("should throw error if file is not json", () => { const input = path.join(__dirname, "./inputs/non-json-file.txt"); @@ -49,6 +84,14 @@ describe("ConvertCommandForJsdoc", () => { console.log(expectedJSDocOutput2); expect(consoleSpy).toHaveBeenCalledWith(expectedJSDocOutput2); }); + + it("should directory and output JSDoc type definitions", () => { + const consoleSpy = spyOn(console, "log"); + const inputDir = path.join(__dirname, "./inputs"); + convert({ inputDir, name: "MyType", format: "jsdoc" }); + console.log(expectedDirectoryToJSDoc); + expect(consoleSpy).toHaveBeenCalledWith(expectedDirectoryToJSDoc); + }); }); describe("ConvertCommandForTypescript", () => { @@ -59,23 +102,31 @@ describe("ConvertCommandForTypescript", () => { console.log(expectedTsOutput2); expect(consoleSpy).toHaveBeenCalledWith(expectedTsOutput2); }); + + it("should read directory and output typescript interfaces", () => { + const consoleSpy = spyOn(console, "log"); + const inputDir = path.join(__dirname, "./inputs"); + convert({ inputDir, name: "MyType", format: "ts" }); + console.log(expectedDirectoryToJSDoc); + expect(consoleSpy).toHaveBeenCalledWith(expectedDirectoryToJSDoc); + }); }); describe("ConvertCommandForSchema", () => { it("should read JSON file and output json schema", () => { const consoleSpy = spyOn(console, "log"); const input = path.join(__dirname, "./inputs/mock3.json"); - convert({ input, format: "ts" }); + convert({ input, format: "schema" }); console.log(expectedSchemaOutput); expect(consoleSpy).toHaveBeenCalledWith(expectedSchemaOutput); }); }); describe("ConvertCommandWithOutputFile", () => { - it("should create output file", async () => { + it("should create output file from input file", async () => { const inputPath = path.join(__dirname, "./inputs/mock2.json"); const outputPath = path.join(__dirname, "./outputs/mock2.ts"); - convert({ + await convert({ input: inputPath, name: "MyType", format: "ts", @@ -89,4 +140,47 @@ describe("ConvertCommandWithOutputFile", () => { expect(outputData.trim()).toBe(expectedTsOutput2.trim()); }); + + it("should create output file from input directory", async () => { + const inputDirPath = path.join(__dirname, "./inputs"); + const outputPath = path.join(__dirname, "./outputs/mock-directory.ts"); + await convert({ + inputDir: inputDirPath, + name: "MyDirectoryType", + format: "ts", + output: outputPath, + }); + const isOutputFileExists = await access(outputPath) + .then(() => true) + .catch(() => false); + expect(isOutputFileExists).toBeTrue(); + const outputData = fs.readFileSync(outputPath, "utf-8"); + + expect(outputData.trim()).toBe(expectedDirectoryToTs); + }); +}); + +describe("ConvertCommandDirectoryOverFile", () => { + it("should convert directory over file if passed -I and -i arguments", async () => { + const inputDirPath = path.join(__dirname, "./inputs"); + const inputPath = path.join(__dirname, "./inputs/mock2.json"); + const outputPath = path.join( + __dirname, + "./outputs/mock-directory-over-file.ts" + ); + await convert({ + inputDir: inputDirPath, + input: inputPath, + name: "MyDirectoryOverFileType", + format: "ts", + output: outputPath, + }); + const isOutputFileExists = await access(outputPath) + .then(() => true) + .catch(() => false); + expect(isOutputFileExists).toBeTrue(); + const outputData = fs.readFileSync(outputPath, "utf-8"); + + expect(outputData.trim().length).toEqual(expectedDirectoryOverFileToTs.trim().length); + }); }); diff --git a/tests/e2e.test.js b/tests/e2e.test.js index cbe24ca..d31acde 100644 --- a/tests/e2e.test.js +++ b/tests/e2e.test.js @@ -1,14 +1,30 @@ import { $ } from "bun"; -import { describe, expect, it } from "bun:test"; +import { describe, expect, it, beforeAll } from "bun:test"; import fs from "fs"; +import path from "path"; import { expectedE2EOutput } from "tests/expected-e2e-output"; -import { expectedJSDocOutput2, expectedJSDocOutput3 } from "tests/expected-js-doc-output"; -import { expectedTsOutput3 } from "tests/expected-ts-output"; +import { + expectedDirectoryToJSDoc, + expectedJSDocOutput2, + expectedJSDocOutput3, +} from "tests/expected-js-doc-output"; +import { + expectedDirectoryToTs, + expectedTsOutput3, +} from "tests/expected-ts-output"; import expectedSchemaOutput from "tests/expected-schema-output.json"; +beforeAll(async () => { + const emptyDir = path.join(__dirname, "./inputs/empty-directory"); + const isDirExists = await fs.existsSync(emptyDir); + if (!isDirExists) { + await fs.mkdirSync(emptyDir); + } +}); + describe("End-to-End Tests", () => { it("should print help when zero arguments passed", async () => { - const output = (await $`bun run dist/index.js`.text()).trim(); + const output = (await $`bun start`.text()).trim(); const expectedOutput = expectedE2EOutput.trim(); expect(output).toBe(expectedOutput); }); @@ -22,6 +38,24 @@ describe("End-to-End Tests", () => { expect(output.trim()).toBe(expectedJSDocOutput2.trim()); }); + it("should convert directory to JSDoc types and write to output file when --format is missing", async () => { + const inputDirPath = "tests/inputs"; + const outputPath = "tests/outputs/mock2.js"; + await $`bun start -I ${inputDirPath} -o ${outputPath}`; + + const output = fs.readFileSync(outputPath, "utf-8"); + expect(output.trim()).toBe(expectedDirectoryToJSDoc.trim()); + }); + + it("should convert directory to TS types and write to output file when --format is ts", async () => { + const inputDirPath = "tests/inputs"; + const outputPath = "tests/outputs/mock-directory.ts"; + await $`bun start -I ${inputDirPath} -o ${outputPath} --format ts --name MyDirectoryType`; + + const output = fs.readFileSync(outputPath, "utf-8"); + expect(output.trim()).toBe(expectedDirectoryToTs.trim()); + }); + it("should convert JSON to JSDoc types and print output", async () => { const inputPath = "tests/inputs/mock2.json"; const output = await $`bun start -i ${inputPath} --format jsdoc`.text(); @@ -31,7 +65,8 @@ describe("End-to-End Tests", () => { it("should convert JSON to TS types and print output", async () => { const inputPath = "tests/inputs/mock3.json"; - const output = await $`bun start -i ${inputPath} --name IExample --format ts`.text(); + const output = + await $`bun start -i ${inputPath} --name IExample --format ts`.text(); expect(output.trim()).toBe(expectedTsOutput3.trim()); }); diff --git a/tests/expected-directory-to-json-output.js b/tests/expected-directory-to-json-output.js new file mode 100644 index 0000000..19d56af --- /dev/null +++ b/tests/expected-directory-to-json-output.js @@ -0,0 +1,18 @@ +export const expectedDirectoryToJsonOutput = { + mocks: { + mockThree: + '{\n "home": {\n "seo": {\n "title": "Agence Mosi"\n },\n "content": {\n "activities": ["évènementiel", "marketing", "communication digitale"],\n "motto": ["être meilleur", "pour les autres"],\n "communication": "communication"\n }\n },\n "about": {\n "seo": {\n "title": "A propos de nous"\n },\n "content": {\n "title": ["qui sommes", "nous ?"],\n "presentation": "MOSI Agency » L\'Expertise IT Services 36O°.",\n "specialization": "MOSI AGENCY est une entreprise spécialisée dans la communication globale offrant une vaste gamme de services, notamment la production de supports visuels diversifiés et des conseils en communication.",\n "expertise": "Notre expertise s\'étend également à l\'ingénierie informatique, permettant des solutions novatrices à moindre coût. Dans ce domaine, nos services incluent :",\n "sections": [\n {\n "label": "ingénierie informatique",\n "items": [\n "Développement de logiciels sur mesure",\n "Solutions informatiques adaptées aux besoins spécifiques",\n "Intégration de systèmes"\n ]\n },\n {\n "label": "services de communication",\n "items": [\n "Production de supports visuels variés",\n "Conseil en stratégies de communication",\n "Conception de publicités",\n "Reportages",\n "Community Management",\n "Création de sites web"\n ]\n }\n ]\n }\n },\n "contact": {\n "seo": {\n "title": "Contactez-nous"\n },\n "content": {\n "address": "Meudon la forêt, 92360",\n "telephone": "+33663741976",\n "email": "contact@mosi-agency",\n "partnership": {\n "label": "En partenariat avec :",\n "name": "icell technologies"\n }\n }\n },\n "services": {\n "seo": {\n "title": "Nos Services"\n },\n "content": {\n "title": "nos services",\n "items": [\n "organisation de célébrations festives",\n "organisation d\'événements culturels",\n "mise en place d\'un réseau d\'influence",\n "coordination d\'une compagne marketing",\n "optimisation sur google (seo)",\n "services google analytics",\n "google ads",\n "publicité payante sur les réseaux sociaux (meta ads)",\n "marketing des réseaux sociaux",\n "personal branding",\n "création de contenus",\n "conception et développement de sites web",\n "développement d\'applications mobiles",\n "hébergement web & nom de domaine",\n "adresses e-mails personnalisées",\n "design graphique (infographie)",\n "design de documents d\'entreprises (en-tête, factures,..)",\n "publicités & reportages",\n "infogérance (maintenance matérielle & logicielle)"\n ]\n }\n },\n "notFound": {\n "seo": {\n "title": "Page non trouvée"\n }\n },\n "menu": [\n { "key": "home", "link": "/", "text": "accueil" },\n { "key": "about", "link": "/a-propos-de-nous", "text": "a propos de nous" },\n { "key": "contact", "link": "/contact", "text": "contact" },\n { "key": "services", "link": "/services", "text": "nos services" },\n {\n "key": "pricing",\n "link": "/nos-tarifs",\n "text": "tarifs",\n "children": [\n {\n "key": "event-planning",\n "link": "/event-planning",\n "text": "service évènementiel"\n },\n {\n "key": "marketing-campaign",\n "link": "/marketing-campaign",\n "text": "campagnes marketing"\n },\n {\n "key": "design-and-multimedia",\n "link": "/design-and-multimedia",\n "text": "design & multimedia"\n },\n {\n "key": "social-media-management",\n "link": "/social-media-management",\n "text": "gestion social media"\n },\n {\n "key": "website-conception",\n "link": "/website-conception",\n "text": "création sites web"\n }\n ]\n }\n ],\n "footer": {\n "copyright": "Tout droits réservés",\n "navigation": [\n { "key": "sitemap", "link": "/sitemap", "text": "Plan du site" },\n {\n "key": "legal-notice",\n "link": "/legal-notice",\n "text": "Mentions légales"\n },\n {\n "key": "terms-conditions",\n "link": "/terms-conditions",\n "text": "Conditions générales"\n },\n {\n "key": "data-protection",\n "link": "/data-protection",\n "text": "Protection des données"\n },\n {\n "key": "cookie-management",\n "link": "/cookie-management",\n "text": "Paramètres des cookies"\n },\n {\n "key": "faq",\n "link": "/faq",\n "text": "FAQ"\n }\n ],\n "socialNetworks": [\n { "key": "twitter", "link": "#", "icon": "twitter-x-fill" },\n { "key": "instagram", "link": "#", "icon": "instagram-fill" },\n { "key": "linkedin", "link": "#", "icon": "linkedin-box-fill" }\n ]\n }\n}\n', + mockOne: + '{\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n}\n', + mock_two: + '{\n "home": {\n "seo": { "title": "Test" },\n "content": { "activities": ["a", "b"] }\n }\n}\n', + }, + emptyDirectory: {}, + mock1: + '{\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n}\n', + mock2: + '{\n "home": {\n "seo": { "title": "Test" },\n "content": { "activities": ["a", "b"] }\n }\n}\n', + emptyObject: "{}", + mock3: + '{\n "home": {\n "seo": {\n "title": "Agence Mosi"\n },\n "content": {\n "activities": ["évènementiel", "marketing", "communication digitale"],\n "motto": ["être meilleur", "pour les autres"],\n "communication": "communication"\n }\n },\n "about": {\n "seo": {\n "title": "A propos de nous"\n },\n "content": {\n "title": ["qui sommes", "nous ?"],\n "presentation": "MOSI Agency » L’Expertise IT Services 36O°.",\n "specialization": "MOSI AGENCY est une entreprise spécialisée dans la communication globale offrant une vaste gamme de services, notamment la production de supports visuels diversifiés et des conseils en communication.",\n "expertise": "Notre expertise s\'étend également à l\'ingénierie informatique, permettant des solutions novatrices à moindre coût. Dans ce domaine, nos services incluent :",\n "sections": [\n {\n "label": "ingénierie informatique",\n "items": [\n "Développement de logiciels sur mesure",\n "Solutions informatiques adaptées aux besoins spécifiques",\n "Intégration de systèmes"\n ]\n },\n {\n "label": "services de communication",\n "items": [\n "Production de supports visuels variés",\n "Conseil en stratégies de communication",\n "Conception de publicités",\n "Reportages",\n "Community Management",\n "Création de sites web"\n ]\n }\n ]\n }\n },\n "contact": {\n "seo": {\n "title": "Contactez-nous"\n },\n "content": {\n "address": "Meudon la forêt, 92360",\n "telephone": "+33663741976",\n "email": "contact@mosi-agency",\n "partnership": {\n "label": "En partenariat avec :",\n "name": "icell technologies"\n }\n }\n },\n "services": {\n "seo": {\n "title": "Nos Services"\n },\n "content": {\n "title": "nos services",\n "items": [\n "organisation de célébrations festives",\n "organisation d\'événements culturels",\n "mise en place d\'un réseau d\'influence",\n "coordination d\'une compagne marketing",\n "optimisation sur google (seo)",\n "services google analytics",\n "google ads",\n "publicité payante sur les réseaux sociaux (meta ads)",\n "marketing des réseaux sociaux",\n "personal branding",\n "création de contenus",\n "conception et développement de sites web",\n "développement d\'applications mobiles",\n "hébergement web & nom de domaine",\n "adresses e-mails personnalisées",\n "design graphique (infographie)",\n "design de documents d\'entreprises (en-tête, factures,..)",\n "publicités & reportages",\n "infogérance (maintenance matérielle & logicielle)"\n ]\n }\n },\n "notFound": {\n "seo": {\n "title": "Page non trouvée"\n }\n },\n "menu": [\n { "key": "home", "link": "/", "text": "accueil" },\n { "key": "about", "link": "/a-propos-de-nous", "text": "a propos de nous" },\n { "key": "contact", "link": "/contact", "text": "contact" },\n { "key": "services", "link": "/services", "text": "nos services" },\n {\n "key": "pricing",\n "link": "/nos-tarifs",\n "text": "tarifs",\n "children": [\n {\n "key": "event-planning",\n "link": "/event-planning",\n "text": "service évènementiel"\n },\n {\n "key": "marketing-campaign",\n "link": "/marketing-campaign",\n "text": "campagnes marketing"\n },\n {\n "key": "design-and-multimedia",\n "link": "/design-and-multimedia",\n "text": "design & multimedia"\n },\n {\n "key": "social-media-management",\n "link": "/social-media-management",\n "text": "gestion social media"\n },\n {\n "key": "website-conception",\n "link": "/website-conception",\n "text": "création sites web"\n }\n ]\n }\n ],\n "footer": {\n "copyright": "Tout droits réservés",\n "navigation": [\n { "key": "sitemap", "link": "/sitemap", "text": "Plan du site" },\n {\n "key": "legal-notice",\n "link": "/legal-notice",\n "text": "Mentions légales"\n },\n {\n "key": "terms-conditions",\n "link": "/terms-conditions",\n "text": "Conditions générales"\n },\n {\n "key": "data-protection",\n "link": "/data-protection",\n "text": "Protection des données"\n },\n {\n "key": "cookie-management",\n "link": "/cookie-management",\n "text": "Paramètres des cookies"\n },\n {\n "key": "faq",\n "link": "/faq",\n "text": "FAQ"\n }\n ],\n "socialNetworks": [\n { "key": "twitter", "link": "#", "icon": "twitter-x-fill" },\n { "key": "instagram", "link": "#", "icon": "instagram-fill" },\n { "key": "linkedin", "link": "#", "icon": "linkedin-box-fill" }\n ]\n }\n}\n', +}; diff --git a/tests/expected-e2e-output.js b/tests/expected-e2e-output.js index a9db2d1..ecfdf53 100644 --- a/tests/expected-e2e-output.js +++ b/tests/expected-e2e-output.js @@ -3,10 +3,12 @@ export const expectedE2EOutput = `Usage: blk [options] Transform json file into jsdoc or typescript definition Options: - -v, --version display the version number - -i, --input input JSON file - -o, --output output file - --name name of the type - --format output format (jsdoc, ts or schema) (default: "jsdoc") - --watch watch for changes - -h, --help display help for command`; \ No newline at end of file + -v, --version display the version number + -i, --input input JSON file + -I, --input-dir input directory containing directories and json files + -o, --output output file + --name name of the type + --format output format (jsdoc, ts or schema) (default: + "jsdoc") + --watch watch for changes + -h, --help display help for command`; \ No newline at end of file diff --git a/tests/expected-js-doc-output.js b/tests/expected-js-doc-output.js index 866c49e..13b2fa8 100644 --- a/tests/expected-js-doc-output.js +++ b/tests/expected-js-doc-output.js @@ -66,4 +66,18 @@ export const expectedJSDocOutput2 = `/** * @property {string} footer.socialNetworks[].link * @property {string} footer.socialNetworks[].icon */ +`; + +export const expectedDirectoryToJSDoc = `/** + * @typedef {object} MyType + * @property {object} emptyDirectory + * @property {string} emptyObject + * @property {string} mock1 + * @property {string} mock2 + * @property {string} mock3 + * @property {object} mocks + * @property {string} mocks.mockThree + * @property {string} mocks.mockOne + * @property {string} mocks.mock_two + */ `; \ No newline at end of file diff --git a/tests/expected-ts-output.js b/tests/expected-ts-output.js index c3e9c02..b6f17be 100644 --- a/tests/expected-ts-output.js +++ b/tests/expected-ts-output.js @@ -136,4 +136,40 @@ export interface HomeSeo { title: string; } -`; \ No newline at end of file +`; + +export const expectedDirectoryToTs = `export interface MyDirectoryType { + emptyDirectory: EmptyDirectory; + emptyObject: string; + mock1: string; + mock2: string; + mock3: string; + mocks: Mocks; +} + +export interface Mocks { + mockThree: string; + mockOne: string; + mock_two: string; +} + +export interface EmptyDirectory { +}`; + +export const expectedDirectoryOverFileToTs = `export interface MyDirectoryOverFileType { + emptyDirectory: EmptyDirectory; + emptyObject: string; + mock1: string; + mock2: string; + mock3: string; + mocks: Mocks; +} + +export interface Mocks { + mockThree: string; + mock_two: string; + mockOne: string; +} + +export interface EmptyDirectory { +}`; \ No newline at end of file diff --git a/tests/inputs/empty-object.json b/tests/inputs/empty-object.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/tests/inputs/empty-object.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests/inputs/mocks/mock-three.json b/tests/inputs/mocks/mock-three.json new file mode 100644 index 0000000..55a395e --- /dev/null +++ b/tests/inputs/mocks/mock-three.json @@ -0,0 +1,166 @@ +{ + "home": { + "seo": { + "title": "Agence Mosi" + }, + "content": { + "activities": ["évènementiel", "marketing", "communication digitale"], + "motto": ["être meilleur", "pour les autres"], + "communication": "communication" + } + }, + "about": { + "seo": { + "title": "A propos de nous" + }, + "content": { + "title": ["qui sommes", "nous ?"], + "presentation": "MOSI Agency » L'Expertise IT Services 36O°.", + "specialization": "MOSI AGENCY est une entreprise spécialisée dans la communication globale offrant une vaste gamme de services, notamment la production de supports visuels diversifiés et des conseils en communication.", + "expertise": "Notre expertise s'étend également à l'ingénierie informatique, permettant des solutions novatrices à moindre coût. Dans ce domaine, nos services incluent :", + "sections": [ + { + "label": "ingénierie informatique", + "items": [ + "Développement de logiciels sur mesure", + "Solutions informatiques adaptées aux besoins spécifiques", + "Intégration de systèmes" + ] + }, + { + "label": "services de communication", + "items": [ + "Production de supports visuels variés", + "Conseil en stratégies de communication", + "Conception de publicités", + "Reportages", + "Community Management", + "Création de sites web" + ] + } + ] + } + }, + "contact": { + "seo": { + "title": "Contactez-nous" + }, + "content": { + "address": "Meudon la forêt, 92360", + "telephone": "+33663741976", + "email": "contact@mosi-agency", + "partnership": { + "label": "En partenariat avec :", + "name": "icell technologies" + } + } + }, + "services": { + "seo": { + "title": "Nos Services" + }, + "content": { + "title": "nos services", + "items": [ + "organisation de célébrations festives", + "organisation d'événements culturels", + "mise en place d'un réseau d'influence", + "coordination d'une compagne marketing", + "optimisation sur google (seo)", + "services google analytics", + "google ads", + "publicité payante sur les réseaux sociaux (meta ads)", + "marketing des réseaux sociaux", + "personal branding", + "création de contenus", + "conception et développement de sites web", + "développement d'applications mobiles", + "hébergement web & nom de domaine", + "adresses e-mails personnalisées", + "design graphique (infographie)", + "design de documents d'entreprises (en-tête, factures,..)", + "publicités & reportages", + "infogérance (maintenance matérielle & logicielle)" + ] + } + }, + "notFound": { + "seo": { + "title": "Page non trouvée" + } + }, + "menu": [ + { "key": "home", "link": "/", "text": "accueil" }, + { "key": "about", "link": "/a-propos-de-nous", "text": "a propos de nous" }, + { "key": "contact", "link": "/contact", "text": "contact" }, + { "key": "services", "link": "/services", "text": "nos services" }, + { + "key": "pricing", + "link": "/nos-tarifs", + "text": "tarifs", + "children": [ + { + "key": "event-planning", + "link": "/event-planning", + "text": "service évènementiel" + }, + { + "key": "marketing-campaign", + "link": "/marketing-campaign", + "text": "campagnes marketing" + }, + { + "key": "design-and-multimedia", + "link": "/design-and-multimedia", + "text": "design & multimedia" + }, + { + "key": "social-media-management", + "link": "/social-media-management", + "text": "gestion social media" + }, + { + "key": "website-conception", + "link": "/website-conception", + "text": "création sites web" + } + ] + } + ], + "footer": { + "copyright": "Tout droits réservés", + "navigation": [ + { "key": "sitemap", "link": "/sitemap", "text": "Plan du site" }, + { + "key": "legal-notice", + "link": "/legal-notice", + "text": "Mentions légales" + }, + { + "key": "terms-conditions", + "link": "/terms-conditions", + "text": "Conditions générales" + }, + { + "key": "data-protection", + "link": "/data-protection", + "text": "Protection des données" + }, + { + "key": "cookie-management", + "link": "/cookie-management", + "text": "Paramètres des cookies" + }, + { + "key": "faq", + "link": "/faq", + "text": "FAQ" + } + ], + "socialNetworks": [ + { "key": "twitter", "link": "#", "icon": "twitter-x-fill" }, + { "key": "instagram", "link": "#", "icon": "instagram-fill" }, + { "key": "linkedin", "link": "#", "icon": "linkedin-box-fill" } + ] + } +} diff --git a/tests/inputs/mocks/mock.one.json b/tests/inputs/mocks/mock.one.json new file mode 100644 index 0000000..dd0f142 --- /dev/null +++ b/tests/inputs/mocks/mock.one.json @@ -0,0 +1,6 @@ +{ + "userId": 1, + "id": 1, + "title": "delectus aut autem", + "completed": false +} diff --git a/tests/inputs/mocks/mock_two.json b/tests/inputs/mocks/mock_two.json new file mode 100644 index 0000000..144435a --- /dev/null +++ b/tests/inputs/mocks/mock_two.json @@ -0,0 +1,6 @@ +{ + "home": { + "seo": { "title": "Test" }, + "content": { "activities": ["a", "b"] } + } +} diff --git a/tests/outputs/mock-directory-over-file.ts b/tests/outputs/mock-directory-over-file.ts new file mode 100644 index 0000000..95bea8e --- /dev/null +++ b/tests/outputs/mock-directory-over-file.ts @@ -0,0 +1,18 @@ +export interface MyDirectoryOverFileType { + emptyDirectory: EmptyDirectory; + emptyObject: string; + mock1: string; + mock2: string; + mock3: string; + mocks: Mocks; +} + +export interface Mocks { + mockThree: string; + mockOne: string; + mock_two: string; +} + +export interface EmptyDirectory { +} + diff --git a/tests/outputs/mock-directory.ts b/tests/outputs/mock-directory.ts new file mode 100644 index 0000000..5840465 --- /dev/null +++ b/tests/outputs/mock-directory.ts @@ -0,0 +1,18 @@ +export interface MyDirectoryType { + emptyDirectory: EmptyDirectory; + emptyObject: string; + mock1: string; + mock2: string; + mock3: string; + mocks: Mocks; +} + +export interface Mocks { + mockThree: string; + mockOne: string; + mock_two: string; +} + +export interface EmptyDirectory { +} + diff --git a/tests/outputs/mock2.js b/tests/outputs/mock2.js index 015e112..6a254fe 100644 --- a/tests/outputs/mock2.js +++ b/tests/outputs/mock2.js @@ -1,8 +1,12 @@ /** * @typedef {object} MyType - * @property {object} home - * @property {object} home.seo - * @property {string} home.seo.title - * @property {object} home.content - * @property {string[]} home.content.activities + * @property {object} emptyDirectory + * @property {string} emptyObject + * @property {string} mock1 + * @property {string} mock2 + * @property {string} mock3 + * @property {object} mocks + * @property {string} mocks.mockThree + * @property {string} mocks.mockOne + * @property {string} mocks.mock_two */ diff --git a/tests/utils.test.js b/tests/utils.test.js new file mode 100644 index 0000000..8230c56 --- /dev/null +++ b/tests/utils.test.js @@ -0,0 +1,28 @@ +import { organizeToJSON } from "$lib/utils"; +import { describe, expect, it, beforeAll } from "bun:test"; +import { existsSync, mkdirSync } from "fs"; +import path from "path"; +import { expectedDirectoryToJsonOutput } from "tests/expected-directory-to-json-output"; + +beforeAll(async () => { + const emptyDir = path.join(__dirname, "./inputs/empty-directory"); + const isDirExists = await existsSync(emptyDir); + if (!isDirExists) { + await mkdirSync(emptyDir); + } +}); + +describe("DirectoryToJSON", () => { + it("should return a json object", async () => { + const inputDir = path.join(__dirname, "./inputs"); + const directoryJSON = await organizeToJSON(inputDir); + expect(directoryJSON).toEqual(expectedDirectoryToJsonOutput); + }); + + it("should return empty object when directory is empty", async () => { + const inputDir = path.join(__dirname, "./inputs/empty-directory"); + const directoryJSON = await organizeToJSON(inputDir); + const expectedOutput = {}; + expect(directoryJSON).toStrictEqual(expectedOutput); + }); +});