diff --git a/package-lock.json b/package-lock.json index 801cbd4b5..bc45dd3a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "a0deploy": "lib/index.js" }, "devDependencies": { + "@types/fs-extra": "^9.0.13", "@types/lodash": "^4.14.185", "@types/mocha": "^9.1.0", "@types/nconf": "^0.10.3", @@ -897,6 +898,15 @@ "@types/express": "*" } }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -7385,6 +7395,15 @@ "@types/express": "*" } }, + "@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", diff --git a/package.json b/package.json index 9ee72e370..148f4d3c3 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "yargs": "^15.3.1" }, "devDependencies": { + "@types/fs-extra": "^9.0.13", "@types/lodash": "^4.14.185", "@types/mocha": "^9.1.0", "@types/nconf": "^0.10.3", diff --git a/src/context/directory/handlers/databases.ts b/src/context/directory/handlers/databases.ts index c3c83dc45..c27e46b5b 100644 --- a/src/context/directory/handlers/databases.ts +++ b/src/context/directory/handlers/databases.ts @@ -125,13 +125,14 @@ async function dump(context: DirectoryContext): Promise { // customScripts option only written if there are scripts ...(database.options.customScripts && { customScripts: Object.entries(database.options.customScripts) - //@ts-ignore + //@ts-ignore because we'll fix this in subsequent PR .sort(sortCustomScripts) .reduce((scripts, [name, script]) => { // Dump custom script to file const scriptName = sanitize(`${name}.js`); const scriptFile = path.join(dbFolder, scriptName); log.info(`Writing ${scriptFile}`); + //@ts-ignore because we'll fix this in subsequent PR fs.writeFileSync(scriptFile, script); scripts[name] = `./${scriptName}`; return scripts; diff --git a/src/context/directory/handlers/pages.ts b/src/context/directory/handlers/pages.ts index a34698058..bd4effbe1 100644 --- a/src/context/directory/handlers/pages.ts +++ b/src/context/directory/handlers/pages.ts @@ -6,9 +6,10 @@ import log from '../../../logger'; import { getFiles, existsMustBeDir, dumpJSON, loadJSON } from '../../../utils'; import { DirectoryHandler } from '.'; import DirectoryContext from '..'; -import { Asset, ParsedAsset } from '../../../types'; +import { ParsedAsset } from '../../../types'; +import { Page } from '../../../tools/auth0/handlers/pages'; -type ParsedPages = ParsedAsset<'pages', Asset[]>; +type ParsedPages = ParsedAsset<'pages', Page[]>; function parse(context: DirectoryContext): ParsedPages { const pagesFolder = path.join(context.filePath, constants.PAGES_DIRECTORY); @@ -29,7 +30,7 @@ function parse(context: DirectoryContext): ParsedPages { return acc; }, {}); - const pages = Object.keys(sorted).flatMap((key): Asset[] => { + const pages = Object.keys(sorted).flatMap((key): Page[] => { const { meta, html } = sorted[key]; if (!meta) { log.warn(`Skipping pages file ${html} as missing the corresponding '.json' file`); @@ -60,24 +61,21 @@ function parse(context: DirectoryContext): ParsedPages { async function dump(context: DirectoryContext): Promise { const pages = context.assets.pages; - if (!pages) return; // Skip, nothing to dump + if (!pages) return; - // Create Pages folder const pagesFolder = path.join(context.filePath, constants.PAGES_DIRECTORY); fs.ensureDirSync(pagesFolder); pages.forEach((page) => { - var metadata = { ...page }; + const metadata = { ...page }; - if (page.name !== 'error_page' || page.html !== undefined) { - // Dump template html to file + if (page.html !== undefined) { const htmlFile = path.join(pagesFolder, `${page.name}.html`); log.info(`Writing ${htmlFile}`); - fs.writeFileSync(htmlFile || '', page.html); + fs.writeFileSync(htmlFile, page.html); metadata.html = `./${page.name}.html`; } - // Dump page metadata const pageFile = path.join(pagesFolder, `${page.name}.json`); dumpJSON(pageFile, metadata); }); diff --git a/src/context/yaml/handlers/databases.ts b/src/context/yaml/handlers/databases.ts index bf9debb12..cf5ca4f2b 100644 --- a/src/context/yaml/handlers/databases.ts +++ b/src/context/yaml/handlers/databases.ts @@ -58,6 +58,7 @@ async function dump(context: YAMLContext): Promise { // customScripts option only written if there are scripts ...(database.options.customScripts && { customScripts: Object.entries(database.options.customScripts) + //@ts-ignore because we'll fix this in subsequent PR .sort(sortCustomScripts) .reduce((scripts, [name, script]) => { // Create Database folder @@ -69,6 +70,7 @@ async function dump(context: YAMLContext): Promise { const scriptName = sanitize(name); const scriptFile = path.join(dbFolder, `${scriptName}.js`); log.info(`Writing ${scriptFile}`); + //@ts-ignore because we'll fix this in subsequent PR fs.writeFileSync(scriptFile, script); scripts[name] = `./databases/${dbName}/${scriptName}.js`; return scripts; diff --git a/src/context/yaml/handlers/pages.ts b/src/context/yaml/handlers/pages.ts index c04c52265..6b73a2499 100644 --- a/src/context/yaml/handlers/pages.ts +++ b/src/context/yaml/handlers/pages.ts @@ -4,9 +4,10 @@ import fs from 'fs-extra'; import log from '../../../logger'; import { YAMLHandler } from '.'; import YAMLContext from '..'; -import { Asset, ParsedAsset } from '../../../types'; +import { ParsedAsset } from '../../../types'; +import { Page } from '../../../tools/auth0/handlers/pages'; -type ParsedPages = ParsedAsset<'pages', Asset[]>; +type ParsedPages = ParsedAsset<'pages', Page[]>; async function parse(context: YAMLContext): Promise { // Load the HTML file for each page @@ -31,19 +32,17 @@ async function dump(context: YAMLContext): Promise { return { pages: null }; } - // Create Pages folder const pagesFolder = path.join(context.basePath, 'pages'); fs.ensureDirSync(pagesFolder); pages = pages.map((page) => { - if (page.name === 'error_page' && page.html === undefined) { + if (page.html === undefined) { return page; } - // Dump html to file const htmlFile = path.join(pagesFolder, `${page.name}.html`); log.info(`Writing ${htmlFile}`); - fs.writeFileSync(htmlFile, page.html || ''); + fs.writeFileSync(htmlFile, page.html); return { ...page, html: `./pages/${page.name}.html`, diff --git a/src/tools/auth0/handlers/pages.ts b/src/tools/auth0/handlers/pages.ts index 2c0b8dd21..620d1866c 100644 --- a/src/tools/auth0/handlers/pages.ts +++ b/src/tools/auth0/handlers/pages.ts @@ -12,6 +12,14 @@ export const pageNameMap = { error_page: 'error_page', }; +export type Page = { + show_log_link?: boolean; + name: string; + enabled?: boolean; + html?: string; + url?: string; +}; + // With this schema, we can only validate property types but not valid properties on per type basis export const schema = { type: 'array', diff --git a/src/types.ts b/src/types.ts index fed96b97c..c97c9aa87 100644 --- a/src/types.ts +++ b/src/types.ts @@ -8,6 +8,7 @@ import { } from './tools/auth0/handlers/prompts'; import { Tenant } from './tools/auth0/handlers/tenant'; import { Theme } from './tools/auth0/handlers/themes'; +import { Page } from './tools/auth0/handlers/pages'; type SharedPaginationParams = { checkpoint?: boolean; @@ -234,7 +235,7 @@ export type Assets = Partial<{ logStreams: Asset[] | null; migrations: Asset[] | null; organizations: Asset[] | null; - pages: Asset[] | null; + pages: Page[] | null; prompts: Prompts | null; resourceServers: Asset[] | null; roles: Asset[] | null; diff --git a/test/context/directory/pages.test.js b/test/context/directory/pages.test.js index c827ef286..55823eb7a 100644 --- a/test/context/directory/pages.test.js +++ b/test/context/directory/pages.test.js @@ -112,12 +112,11 @@ describe('#directory context pages', () => { context.assets.pages = [ { html: htmlValidation, name: 'login' }, - { html: htmlValidation, name: 'password_reset' }, - { enabled: false, html: htmlValidation, name: 'guardian_multifactor' }, + { enabled: false, html: htmlValidation, name: 'password_reset' }, + { name: 'guardian_multifactor' }, // No `html` property defined { - html: htmlValidation, name: 'error_page', - url: errorPageUrl, + url: errorPageUrl, // URL defined instead of `html` property show_log_link: false, }, ]; @@ -137,29 +136,25 @@ describe('#directory context pages', () => { expect(loadJSON(path.join(pagesFolder, 'password_reset.json'))).to.deep.equal({ html: './password_reset.html', name: 'password_reset', + enabled: false, }); expect(fs.readFileSync(path.join(pagesFolder, 'password_reset.html'), 'utf8')).to.deep.equal( htmlValidation ); expect(loadJSON(path.join(pagesFolder, 'guardian_multifactor.json'))).to.deep.equal({ - html: './guardian_multifactor.html', name: 'guardian_multifactor', - enabled: false, }); - expect( - fs.readFileSync(path.join(pagesFolder, 'guardian_multifactor.html'), 'utf8') - ).to.deep.equal(htmlValidation); + // eslint-disable-next-line no-unused-expressions + expect(fs.existsSync(path.join(pagesFolder, 'guardian_multifactor.html'), 'utf8')).to.be.false; // Should not dump template with no HTML expect(loadJSON(path.join(pagesFolder, 'error_page.json'))).to.deep.equal({ - html: './error_page.html', name: 'error_page', url: errorPageUrl, show_log_link: false, }); - expect(fs.readFileSync(path.join(pagesFolder, 'error_page.html'), 'utf8')).to.deep.equal( - htmlValidation - ); + // eslint-disable-next-line no-unused-expressions + expect(fs.existsSync(path.join(pagesFolder, 'error_page.html'), 'utf8')).to.be.false; // Should not dump template with no HTML }); it('should dump empty error page even if HTML is not set', async () => { diff --git a/test/context/yaml/pages.test.js b/test/context/yaml/pages.test.js index e7bef57d6..f12397fb0 100644 --- a/test/context/yaml/pages.test.js +++ b/test/context/yaml/pages.test.js @@ -145,22 +145,20 @@ describe('#YAML context pages', () => { ); context.assets.pages = [ - { html: undefined, name: 'login' }, // HTML property is not defined here + { name: 'login' }, // HTML property is not defined here ]; const dumped = await handler.dump(context); expect(dumped).to.deep.equal({ pages: [ { - html: './pages/login.html', name: 'login', }, ], }); const pagesFolder = path.join(dir, 'pages'); - expect(fs.readFileSync(path.join(pagesFolder, 'login.html'), 'utf8')).to.deep.equal(''); - expect(fs.readdirSync(pagesFolder).length).to.equal(1); + expect(fs.readdirSync(pagesFolder).length).to.equal(0); }); it('should dump error_page with html undefined', async () => {