@madgex/fert 7.2.1 → 7.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +4 -0
- package/bin/commands/init-template-tasks/copy-theme-files.js +61 -0
- package/bin/commands/init-template-tasks/generate-entry-nunjucks-file.js +64 -0
- package/bin/commands/init-template-tasks/get-macro-name-from-nunjucks.js +13 -0
- package/bin/commands/init-template-tasks/get-nunjucks-dependencies.js +58 -0
- package/bin/commands/init-template-tasks/get-out-dir.js +6 -0
- package/bin/commands/init-template.js +94 -0
- package/bin/utils/cpid-lookup.js +2 -1
- package/bin/utils/get-assets-path.js +4 -4
- package/bin/utils/index.js +1 -26
- package/constants.js +0 -2
- package/docs/FAQ.md +1 -0
- package/docs/README.md +30 -11
- package/package.json +2 -2
- package/repo-template/services/jobseekers-frontend/templates/footer.njk +18 -19
- package/repo-template/services/jobseekers-frontend/templates/header.njk +19 -74
- package/repo-template/services/recruiterservices-frontend/templates/footer.njk +19 -18
- package/repo-template/services/recruiterservices-frontend/templates/header.njk +19 -82
- package/types.d.ts +1 -0
- package/repo-template/services/jobseekers-frontend/templates/context/footer-nav.njk +0 -27
- package/repo-template/services/jobseekers-frontend/templates/context/main-nav.njk +0 -41
- package/repo-template/services/jobseekers-frontend/templates/context/user-nav.njk +0 -17
- package/repo-template/services/jobseekers-frontend/templates/includes/footer-nav.njk +0 -15
- package/repo-template/services/jobseekers-frontend/templates/includes/main-nav.njk +0 -19
- package/repo-template/services/jobseekers-frontend/templates/includes/user-nav/authenticated.njk +0 -34
- package/repo-template/services/jobseekers-frontend/templates/includes/user-nav/unauthenticated.njk +0 -9
- package/repo-template/services/jobseekers-frontend/templates/translations/da.njk +0 -26
- package/repo-template/services/jobseekers-frontend/templates/translations/de.njk +0 -26
- package/repo-template/services/jobseekers-frontend/templates/translations/en.njk +0 -27
- package/repo-template/services/jobseekers-frontend/templates/translations/es.njk +0 -26
- package/repo-template/services/jobseekers-frontend/templates/translations/fr.njk +0 -26
- package/repo-template/services/jobseekers-frontend/templates/translations/nb.njk +0 -26
- package/repo-template/services/jobseekers-frontend/templates/translations/nl.njk +0 -26
- package/repo-template/services/jobseekers-frontend/templates/translations/sv.njk +0 -26
- package/repo-template/services/jobseekers-frontend/templates/translations/zh-cn.njk +0 -26
- package/repo-template/services/recruiterservices-frontend/templates/context/links.njk +0 -136
- package/repo-template/services/recruiterservices-frontend/templates/includes/basket-nav.njk +0 -25
- package/repo-template/services/recruiterservices-frontend/templates/includes/footer/footer-nav.njk +0 -35
- package/repo-template/services/recruiterservices-frontend/templates/includes/footer/social-links.njk +0 -14
- package/repo-template/services/recruiterservices-frontend/templates/includes/primary-nav.njk +0 -18
- package/repo-template/services/recruiterservices-frontend/templates/includes/user-nav/authenticated.njk +0 -36
- package/repo-template/services/recruiterservices-frontend/templates/includes/user-nav/switch-recruiters.njk +0 -13
- package/repo-template/services/recruiterservices-frontend/templates/includes/user-nav/unauthenticated.njk +0 -11
- package/repo-template/services/recruiterservices-frontend/templates/includes/user-nav/user-nav.njk +0 -9
- package/repo-template/services/recruiterservices-frontend/templates/translations/en.njk +0 -29
package/bin/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ import { configsCommand } from './commands/configs.js';
|
|
|
12
12
|
import { initCommand } from './commands/init.js';
|
|
13
13
|
import { validateCommand } from './commands/validate.js';
|
|
14
14
|
import { rootTasksBootstrap } from './commands/_root-tasks-bootstrap.js';
|
|
15
|
+
import { initTemplateCommand } from './commands/init-template.js';
|
|
15
16
|
|
|
16
17
|
const cli = cac('fert');
|
|
17
18
|
|
|
@@ -66,6 +67,9 @@ const run = () => {
|
|
|
66
67
|
.command('init [root]', 'Create a new branding project')
|
|
67
68
|
.option('--cpid <cpid>', 'Specify the clientPropertyId to use in the new project')
|
|
68
69
|
.action((...args) => initCommand(...args));
|
|
70
|
+
cli
|
|
71
|
+
.command('init-template', 'Create a custom template for a specific service & header or footer')
|
|
72
|
+
.action((...args) => initTemplateCommand(...args));
|
|
69
73
|
|
|
70
74
|
cli.command('validate', 'Validate branding project for common issues').action((...args) => validateCommand(...args));
|
|
71
75
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { log } from '../../utils/logging.js';
|
|
4
|
+
import { getOutDir } from './get-out-dir.js';
|
|
5
|
+
import { getNunjucksDependencies } from './get-nunjucks-dependencies.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Collect all dependency include/import files (and all icon .svg files) for a given theme entry file.
|
|
9
|
+
* Copy all these files to new output directory (e.g. service's templates folder)
|
|
10
|
+
* Main macro inside entryFilePath is renamed to `CUSTOM`
|
|
11
|
+
* @param {import('../init-template.js').Result} result
|
|
12
|
+
* @param {string} themeDir absolute dir path of `theme` folder in header footer podlet package
|
|
13
|
+
*/
|
|
14
|
+
export async function copyThemeFiles(result, themeDir) {
|
|
15
|
+
const outDir = getOutDir(result);
|
|
16
|
+
const { entryFilePath } = result.theme;
|
|
17
|
+
// icons are dynamic includes, so we grab all `.svg` icon paths to copy along with everything else
|
|
18
|
+
const iconFiles = await Array.fromAsync(fs.promises.glob(path.resolve(themeDir, '_common/icons/*.svg')));
|
|
19
|
+
|
|
20
|
+
// Discover all dependencies starting from the input file
|
|
21
|
+
const collectedFiles = new Set([...getNunjucksDependencies(entryFilePath), ...iconFiles]);
|
|
22
|
+
|
|
23
|
+
// Create output directory and copy all files
|
|
24
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
25
|
+
|
|
26
|
+
for (const absPath of collectedFiles) {
|
|
27
|
+
const relPath = path.relative(themeDir, absPath);
|
|
28
|
+
const destPath = path.join(outDir, relPath);
|
|
29
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
30
|
+
|
|
31
|
+
const source = fs.readFileSync(absPath, 'utf8');
|
|
32
|
+
let rewritten = prependTemplatePaths(source);
|
|
33
|
+
// this is the entry file
|
|
34
|
+
if (absPath === entryFilePath) {
|
|
35
|
+
rewritten = rewritten.replaceAll(result.theme.themeName, 'CUSTOM');
|
|
36
|
+
}
|
|
37
|
+
fs.writeFileSync(destPath, rewritten, 'utf8');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
log.success(`\nStandalone editable theme initialised at: ${outDir}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Rewrite import/include paths in a file to prepend `templatePath` variable.
|
|
45
|
+
* `templatePath` required to make header-footer-podlet imports work with its HTTP nunjucks loader
|
|
46
|
+
* @param {string} source - The file contents
|
|
47
|
+
* @returns {string} The rewritten source
|
|
48
|
+
*/
|
|
49
|
+
export function prependTemplatePaths(source) {
|
|
50
|
+
return source
|
|
51
|
+
.replace(
|
|
52
|
+
// Rewrite from-imports: {% from 'path' import ... %} -> {% from templatePath + 'path' import ... %}
|
|
53
|
+
/(\{%-?\s*from\s+)/g,
|
|
54
|
+
(match, prefix) => `${prefix}templatePath + `,
|
|
55
|
+
)
|
|
56
|
+
.replace(
|
|
57
|
+
// Rewrite includes: {% include 'path' %} -> {% include templatePath + 'path' %}
|
|
58
|
+
/(\{%-?\s*include\s+)/g,
|
|
59
|
+
(match, prefix) => `${prefix}templatePath + `,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as vite from 'vite';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create new entry file (e.g. `header.njk` or `footer.njk`), pointing at the new copied theme macro called `CUSTOM`.
|
|
7
|
+
* Also generate the correct arguments to pass to the macro using the Storybook story for that theme.
|
|
8
|
+
*
|
|
9
|
+
* @param {import('../init-template.js').Result} result
|
|
10
|
+
* @param {string} hfpDir absolute dir path for header footer podlet package
|
|
11
|
+
* @param {string} themeDir absolute dir path of `theme` folder in header footer podlet package
|
|
12
|
+
* */
|
|
13
|
+
export async function generateEntryNunjucksFile(result, hfpDir, themeDir) {
|
|
14
|
+
const { entryFilePath, type } = result.theme;
|
|
15
|
+
|
|
16
|
+
// the story file for the theme, we are trusting the filename contains the theme name
|
|
17
|
+
const storyFile = path.resolve(hfpDir, `.storybook/stories/components/${result.theme.themeName}.stories.jsx`);
|
|
18
|
+
|
|
19
|
+
/** Bundle the story file as CJS, stubbing out ServerRender to avoid React requirement */
|
|
20
|
+
const response = await vite.build({
|
|
21
|
+
root: path.dirname(storyFile),
|
|
22
|
+
plugins: [
|
|
23
|
+
{
|
|
24
|
+
name: 'stub-server-render',
|
|
25
|
+
enforce: 'pre',
|
|
26
|
+
resolveId(source) {
|
|
27
|
+
// replace import with replaceable string
|
|
28
|
+
if (source.endsWith('ServerRender.jsx')) return '\0server-render-stub';
|
|
29
|
+
return undefined;
|
|
30
|
+
},
|
|
31
|
+
load(id) {
|
|
32
|
+
// replace string with empty const, we don't need/want `ServerRender`, we just want `default.args` object
|
|
33
|
+
if (id === '\0server-render-stub') return 'export const ServerRender = null;';
|
|
34
|
+
return undefined;
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
build: {
|
|
39
|
+
lib: { entry: storyFile, formats: ['cjs'] },
|
|
40
|
+
write: false,
|
|
41
|
+
rollupOptions: { output: { sourcemap: false, manualChunks: undefined } }, // all in 1 "file" output
|
|
42
|
+
},
|
|
43
|
+
logLevel: 'silent',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/** Evaluate the CJS bundle to extract the default export's args */
|
|
47
|
+
const code = response[0].output[0].code;
|
|
48
|
+
const moduleExports = {};
|
|
49
|
+
const moduleObj = { exports: moduleExports };
|
|
50
|
+
new Function('exports', 'module', code)(moduleExports, moduleObj);
|
|
51
|
+
|
|
52
|
+
const args = moduleObj.exports.default.args;
|
|
53
|
+
|
|
54
|
+
args.serviceName = result.service.serviceConfig.serviceName;
|
|
55
|
+
|
|
56
|
+
const rootDir = path.resolve(result.service.dir, 'templates');
|
|
57
|
+
const relEntryFilePath = path.relative(themeDir, entryFilePath);
|
|
58
|
+
const argsJson = JSON.stringify(args, null, 4);
|
|
59
|
+
fs.writeFileSync(
|
|
60
|
+
path.join(rootDir, `${type}.njk`),
|
|
61
|
+
`{% from templatePath + './custom-${type}/${relEntryFilePath}' import CUSTOM with context %}\n{{ CUSTOM(${argsJson}) }}\n`,
|
|
62
|
+
{ encoding: 'utf-8' },
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as parser from 'nunjucks/src/parser.js';
|
|
2
|
+
import * as nodes from 'nunjucks/src/nodes.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extract the first macro name from a template source.
|
|
6
|
+
* @param {string} source - The file contents
|
|
7
|
+
*/
|
|
8
|
+
export function getMacroNameFromNunjucks(source) {
|
|
9
|
+
const ast = parser.parse(source);
|
|
10
|
+
const macros = ast.findAll(nodes.Macro);
|
|
11
|
+
if (macros.length === 0) return undefined;
|
|
12
|
+
return macros[0].name.value;
|
|
13
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as parser from 'nunjucks/src/parser.js';
|
|
4
|
+
import * as nodes from 'nunjucks/src/nodes.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Recursively discover all included/imported files using the nunjucks AST, given an entry nunjucks file
|
|
8
|
+
* @param {string} filePath - Absolute path to the entry file
|
|
9
|
+
* @param {Set<string>} _discovered - Set of all discovered absolute file paths
|
|
10
|
+
* @returns {Set<string>} Set of all discovered absolute file paths (including the entry)
|
|
11
|
+
*/
|
|
12
|
+
export function getNunjucksDependencies(filePath, _discovered = new Set()) {
|
|
13
|
+
let discovered = new Set(_discovered);
|
|
14
|
+
if (discovered.has(filePath)) return discovered;
|
|
15
|
+
discovered.add(filePath);
|
|
16
|
+
|
|
17
|
+
if (!fs.existsSync(filePath)) {
|
|
18
|
+
console.error(`WARNING: File not found: ${filePath}`);
|
|
19
|
+
return discovered;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const source = fs.readFileSync(filePath, 'utf8');
|
|
23
|
+
const dir = path.dirname(filePath);
|
|
24
|
+
|
|
25
|
+
for (const relDepPath of getDependencyPaths(source)) {
|
|
26
|
+
const absDepPath = path.resolve(dir, relDepPath);
|
|
27
|
+
discovered = getNunjucksDependencies(absDepPath, discovered);
|
|
28
|
+
}
|
|
29
|
+
return discovered;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Extract all template paths (from imports and includes) from a source string using the AST.
|
|
34
|
+
* Dynamic imports and includes are not collected.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} source - Nunjucks template source
|
|
37
|
+
* @returns {string[]} Array of *relative* paths referenced in the template
|
|
38
|
+
*/
|
|
39
|
+
export function getDependencyPaths(source) {
|
|
40
|
+
const ast = parser.parse(source);
|
|
41
|
+
const paths = [];
|
|
42
|
+
|
|
43
|
+
for (const node of ast.findAll(nodes.FromImport)) {
|
|
44
|
+
// `node.template.value` will not exist for dynamic import
|
|
45
|
+
if (node.template && node.template.value) {
|
|
46
|
+
paths.push(node.template.value);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const node of ast.findAll(nodes.Include)) {
|
|
51
|
+
// `node.template.value` will not exist for dynamic include
|
|
52
|
+
if (node.template && node.template.value) {
|
|
53
|
+
paths.push(node.template.value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return paths;
|
|
58
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import prompts from 'prompts';
|
|
6
|
+
import { findFertConfigDir, loadServiceConfigFiles } from '../utils/index.js';
|
|
7
|
+
import { log } from '../utils/logging.js';
|
|
8
|
+
import { generateEntryNunjucksFile } from './init-template-tasks/generate-entry-nunjucks-file.js';
|
|
9
|
+
import { copyThemeFiles } from './init-template-tasks/copy-theme-files.js';
|
|
10
|
+
import { getMacroNameFromNunjucks } from './init-template-tasks/get-macro-name-from-nunjucks.js';
|
|
11
|
+
|
|
12
|
+
/** header-footer-podlet-server package root dir */
|
|
13
|
+
const hfpDir = path.dirname(fileURLToPath(import.meta.resolve('@private/header-footer-podlet-server/package.json')));
|
|
14
|
+
/** main themes dir inside header-footer-podlet-server */
|
|
15
|
+
const themeDir = path.resolve(hfpDir, 'lib/templates/themes');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Object constructed based on user selection - has all info required to copy a theme's files to the correct service template dir
|
|
19
|
+
* @typedef Result
|
|
20
|
+
* @type {object}
|
|
21
|
+
* @property {{dir:string, serviceConfig: FertServiceConfigFile}} service
|
|
22
|
+
* @property {{type:"header"|"footer", themeName:string, entryFilePath:string}} theme
|
|
23
|
+
* */
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* This command copies theme files from header-footer-podlet, into a service's template folder, to be used standalone for creating custom headers and footers.
|
|
27
|
+
*
|
|
28
|
+
* 1. collect user prompt choices
|
|
29
|
+
* 2. copy theme files based on choices - modified files to use `templatePath` and rename the theme macro name to `CUSTOM`
|
|
30
|
+
* 3. create new entry file (header.njk or footer.njk) pointing to the copied theme files
|
|
31
|
+
*/
|
|
32
|
+
export async function initTemplateCommand(/* options = {} */) {
|
|
33
|
+
/** @type {Result} */
|
|
34
|
+
let result;
|
|
35
|
+
const rootDir = await findFertConfigDir();
|
|
36
|
+
|
|
37
|
+
const serviceConfigs = await loadServiceConfigFiles();
|
|
38
|
+
if (!serviceConfigs.length) {
|
|
39
|
+
log.warn('No services found to populate with template');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** entry file paths for each theme, all entry files are named `header.njk` or `footer.njk` */
|
|
44
|
+
let themes = await Array.fromAsync(fs.promises.glob(path.resolve(themeDir, '**/{header,footer}.njk')));
|
|
45
|
+
// cheeky hardcode exclude `simplified` themes, as they are not really to be used again
|
|
46
|
+
themes = themes.filter((theme) => !theme.includes('simplified'));
|
|
47
|
+
|
|
48
|
+
/** @type {prompts.PromptObject[]} Ask user which service to copy files to, and which theme to copy */
|
|
49
|
+
const promptArr = [
|
|
50
|
+
{
|
|
51
|
+
type: 'select',
|
|
52
|
+
name: 'service',
|
|
53
|
+
message: 'Which Service?',
|
|
54
|
+
choices: serviceConfigs.map((item) => ({
|
|
55
|
+
title: item.serviceConfig.serviceName,
|
|
56
|
+
description: `Found at ${path.relative(rootDir, item?.dir)}`,
|
|
57
|
+
value: item,
|
|
58
|
+
})),
|
|
59
|
+
initial: 0,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: 'select',
|
|
63
|
+
name: 'theme',
|
|
64
|
+
message: 'Which Theme?',
|
|
65
|
+
choices: themes.map((themeEntryFilePath) => {
|
|
66
|
+
// cheekily get `header` or `footer` "type" from the filename, we only collected `header.njk` and `footer.njk` in `themes` glob above.
|
|
67
|
+
const type = path.basename(themeEntryFilePath, '.njk');
|
|
68
|
+
const entryFileNjkSrc = fs.readFileSync(themeEntryFilePath, 'utf-8');
|
|
69
|
+
const themeName = getMacroNameFromNunjucks(entryFileNjkSrc);
|
|
70
|
+
return {
|
|
71
|
+
title: themeName,
|
|
72
|
+
description: `Type: ${type}`,
|
|
73
|
+
value: { type, themeName, entryFilePath: themeEntryFilePath },
|
|
74
|
+
};
|
|
75
|
+
}),
|
|
76
|
+
initial: 0,
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
result = await prompts(promptArr, {
|
|
82
|
+
onCancel: () => {
|
|
83
|
+
throw new Error(chalk.red('✖') + ' Operation cancelled');
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
} catch (cancelled) {
|
|
87
|
+
console.log(cancelled.message);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await copyThemeFiles(result, themeDir);
|
|
92
|
+
|
|
93
|
+
await generateEntryNunjucksFile(result, hfpDir, themeDir);
|
|
94
|
+
}
|
package/bin/utils/cpid-lookup.js
CHANGED
|
@@ -16,8 +16,9 @@ export async function doCpidLookup(clientPropertyId) {
|
|
|
16
16
|
throw new Error(`${results.status} - ${errorMessage}`);
|
|
17
17
|
}
|
|
18
18
|
const { data } = await results.json();
|
|
19
|
-
const { brandName, parentId: parentCpid } = data;
|
|
19
|
+
const { name, brandName, parentId: parentCpid } = data;
|
|
20
20
|
return {
|
|
21
|
+
name,
|
|
21
22
|
brandName,
|
|
22
23
|
parentCpid: parentCpid ?? false,
|
|
23
24
|
rootClientPropertyId: parentCpid ?? clientPropertyId,
|
|
@@ -3,12 +3,12 @@ import { ASSET_RELAY_API } from '../../constants.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* fetch the Absolute URL `publicUrl` from asset-relay API, for current client & service
|
|
5
5
|
*
|
|
6
|
-
* @param {
|
|
6
|
+
* @param {FertConfig} fertConfig
|
|
7
7
|
* @param {object} environment
|
|
8
|
-
* @returns {URL}
|
|
8
|
+
* @returns {URL} Absolute Public URL for assets
|
|
9
9
|
*/
|
|
10
10
|
export async function getAssetsPath(fertConfig, environment) {
|
|
11
|
-
const { clientPropertyId, serviceName, client
|
|
11
|
+
const { clientPropertyId, serviceName, client } = fertConfig;
|
|
12
12
|
|
|
13
13
|
// Try to get existing mapping
|
|
14
14
|
const getUrl = new URL(ASSET_RELAY_API);
|
|
@@ -26,7 +26,7 @@ export async function getAssetsPath(fertConfig, environment) {
|
|
|
26
26
|
clientPropertyId,
|
|
27
27
|
rootClientPropertyId: client.rootClientPropertyId,
|
|
28
28
|
serviceName,
|
|
29
|
-
siteName:
|
|
29
|
+
siteName: client.name || client.brandName,
|
|
30
30
|
}),
|
|
31
31
|
});
|
|
32
32
|
|
package/bin/utils/index.js
CHANGED
|
@@ -12,35 +12,12 @@ import { cpidLookup } from './cpid-lookup.js';
|
|
|
12
12
|
import { cpIdMatchesGitRemote } from './cpid-matches-git-remote.js';
|
|
13
13
|
import { resolveExternalAssets } from './resolve-external-assets.js';
|
|
14
14
|
import { log } from './logging.js';
|
|
15
|
-
import {
|
|
15
|
+
import { VERSION, FERT_CONFIG_FILENAME, FERT_SERVICE_CONFIG_FILENAME } from '../../constants.js';
|
|
16
16
|
|
|
17
17
|
export function printBanner() {
|
|
18
18
|
console.log(`\n${chalk.green.bold('Fert')} v${VERSION}`);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
* get a config values from `clientconfig` from config-api
|
|
23
|
-
*
|
|
24
|
-
* @param {string} cpid
|
|
25
|
-
* @param {Array<string>} configNames array of config keys to fetch from clientconfig
|
|
26
|
-
* @returns
|
|
27
|
-
*/
|
|
28
|
-
export async function getClientConfig(cpid, configNames = []) {
|
|
29
|
-
Hoek.assert(Array.isArray(configNames), 'configNames must be an array');
|
|
30
|
-
|
|
31
|
-
const result = {};
|
|
32
|
-
|
|
33
|
-
for (let config of configNames) {
|
|
34
|
-
const url = CONFIG_API.replace('{configGroup}', 'clientconfig').replace('{cpid}', cpid).replace('{config}', config);
|
|
35
|
-
const { data } = await fetch(url).then((response) => response.json());
|
|
36
|
-
const { value } = data?.[0] || {};
|
|
37
|
-
|
|
38
|
-
result[config] = value;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return result;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
21
|
export async function resolveConfig(options = {}) {
|
|
45
22
|
Hoek.assert(options.serviceName, 'serviceName is required');
|
|
46
23
|
|
|
@@ -96,8 +73,6 @@ export async function resolveConfig(options = {}) {
|
|
|
96
73
|
|
|
97
74
|
fertConfig.externalAssets = resolveExternalAssets(fertConfig.externalAssets);
|
|
98
75
|
|
|
99
|
-
fertConfig.config = await getClientConfig(fertConfig.clientPropertyId, ['JobseekerSiteWebSitePath', 'SiteName']);
|
|
100
|
-
|
|
101
76
|
return {
|
|
102
77
|
...fertConfig,
|
|
103
78
|
rootDir,
|
package/constants.js
CHANGED
|
@@ -47,8 +47,6 @@ export const ASSET_STORE_INVALIDATION_PATH = `/{fertConfig.client.rootClientProp
|
|
|
47
47
|
|
|
48
48
|
export const ASSET_STORE_USER_GUID = 'a386d4b6-f2df-4b80-ad1f-0349e23f530b';
|
|
49
49
|
|
|
50
|
-
export const CONFIG_API =
|
|
51
|
-
'https://configuration-api.job.madgexhosting.net/configs/override/{cpid}/production/{configGroup}/{config}';
|
|
52
50
|
export const CONFIG_DIR = 'config';
|
|
53
51
|
|
|
54
52
|
/** we assume the remove version of a service hosts an api translations endpoint */
|
package/docs/FAQ.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
-
|
package/docs/README.md
CHANGED
|
@@ -12,14 +12,28 @@ Check out [Branding Repository Overview](BRANDING_REPO_OVERVIEW.md) for the stru
|
|
|
12
12
|
|
|
13
13
|
Find out your `Client Property ID` by visiting [Madgexverse](https://madgexverse.job.madgexhosting.net). If your CPID does not exist, you can not work on this Branding Repo right now.
|
|
14
14
|
|
|
15
|
-
This will give you your repository `https://github.com/wiley/madgex-<CPID>` e.g. https://github.com/wiley/madgex-ff6102ff-0f4b-43d1-a2c7-83b835b8dee5. If this is a brand new client, the Branding Repo
|
|
15
|
+
This will give you your repository `https://github.com/wiley/madgex-<CPID>` e.g. https://github.com/wiley/madgex-ff6102ff-0f4b-43d1-a2c7-83b835b8dee5. If this is a brand new client, the Branding Repo will have no branches. See below for empty Branding Repo instructions.
|
|
16
16
|
|
|
17
17
|
> **Ask Systems why, if this is not automatically created**
|
|
18
18
|
|
|
19
|
-
### If you have an **empty** Branding Repo (e.g. a git repository has been set up but has no files)
|
|
19
|
+
### If you have an **empty** Branding Repo (e.g. a git repository has been set up but has no branches or files)
|
|
20
|
+
|
|
21
|
+
To begin with, lets make sure the `master` branch exists (even without commits yet!). Its important that a branch named `master` exists for our infrastructure to work (e.g. jenkins)
|
|
22
|
+
|
|
23
|
+
- After you have checked out an empty Branding Repo, `cd` into your empty git repo folder
|
|
24
|
+
|
|
25
|
+
> Although this should not happen on new Branding Repos: if the current branch says `main` instead of `master`, we need to fix this.
|
|
26
|
+
>
|
|
27
|
+
> - Rename the `main` branch to `master` (I use vscode and right click on the branch and select `rename branch`) or cli `git branch -m main master`
|
|
28
|
+
|
|
29
|
+
- Now we have an empty branch called `master` on our local machine, we can make an empty commit to this branch, then push it to github remote.
|
|
30
|
+
- `git commit --allow-empty -m "init"`
|
|
31
|
+
- `git push -u origin master`
|
|
32
|
+
|
|
33
|
+
Now `master` branch is properly created _and_ exists on github remote (although we have not committed to it yet!), lets do our first bit of work in a new branch. `master` should only be merged into from other branches via PRs in github.
|
|
20
34
|
|
|
21
35
|
- Switch into a new branch
|
|
22
|
-
-
|
|
36
|
+
- Use `npx @madgex/fert init .` and follow the prompts, this will populate your directory, including at least one `service` where the majority of the work will be done.
|
|
23
37
|
- `npm i` install packages.
|
|
24
38
|
|
|
25
39
|
### If you have an existing Branding Repo
|
|
@@ -63,15 +77,20 @@ Themes work better on V5 than Custom templates. If you must create Custom templa
|
|
|
63
77
|
|
|
64
78
|
### Custom template
|
|
65
79
|
|
|
66
|
-
|
|
80
|
+
If the client has pushed us to design a Custom design, then we can create a good starting point by cloning an existing Theme into a branding repo service.
|
|
81
|
+
Then you will have your own copy of the Theme files to modify to match the design.
|
|
82
|
+
|
|
83
|
+
Run the FERT command `init-template` like so `npx @madgex/fert init-template`, in the root of an existing branding repo, and follow the prompts to select the Theme you would like to clone, and which service it will be for.
|
|
84
|
+
|
|
85
|
+
### Available global Nunjucks context
|
|
67
86
|
|
|
68
|
-
|
|
87
|
+
Variables and functions available in the Nunjucks templates.
|
|
69
88
|
|
|
70
|
-
- `templatePath: string`: Required prefix for
|
|
71
|
-
- `path: string`: current page relative URL
|
|
72
|
-
- `normaliseUrl: Filter`: removes trailing slash from a URL, e.g. `{{path === myNavLink | normaliseUrl }}`
|
|
73
|
-
- `getServiceRoute(serviceRouterId,routeId,paramsObj,queryObj):Function` : [
|
|
74
|
-
- `getAbsoluteServiceRoute(serviceRouterId,routeId,paramsObj,queryObj):Function` : [
|
|
89
|
+
- `templatePath: string`: **Required** prefix for `import`/`include` additional Nunjucks files e.g. `{%- from './include/my-other-file.njk' import myOtherFileMacro with context -%} {%- include templatePath + './include/my-other-file.njk' -%}`
|
|
90
|
+
- `path: string`: current page relative URL, (does not have trailing slash!) - use in navigation for current page check
|
|
91
|
+
- `normaliseUrl: Filter`: removes trailing slash from a URL, good for comparison with `path`, e.g. `{{path === myNavLink | normaliseUrl }}`
|
|
92
|
+
- `getServiceRoute(serviceRouterId,routeId,paramsObj,queryObj):Function` : recommended to generate _relative_ URLs from V5 source of truth, instead of hand-written [usage](https://github.com/wiley/madgex-hapi-reverse-router/blob/master/packages/@madgex.hapi-reverse-router/README.md#usage-example)
|
|
93
|
+
- `getAbsoluteServiceRoute(serviceRouterId,routeId,paramsObj,queryObj):Function` : recommended to generate _absolute_ URLs from V5 source of truth, instead of hand-written [usage](https://github.com/wiley/madgex-hapi-reverse-router/blob/master/packages/@madgex.hapi-reverse-router/README.md#usage-example)
|
|
75
94
|
- routeId reference for [`"jobseekersite"`](https://jobseekers-frontend.job.madgexhosting.net/api/routing-table/ff6102ff-0f4b-43d1-a2c7-83b835b8dee5)
|
|
76
95
|
- routeId reference for [`"recruitersite"`](https://recruiterservices-frontend.job.madgexhosting.net/api/routing-table/ff6102ff-0f4b-43d1-a2c7-83b835b8dee5)
|
|
77
96
|
- e.g. `{{getServiceRoute('jobseekersite', 'account.login', {}, {Pipline: path})}}`
|
|
@@ -79,7 +98,7 @@ Available global Nunjucks context:
|
|
|
79
98
|
|
|
80
99
|
## 🔠 Service V6 translations
|
|
81
100
|
|
|
82
|
-
Each `service` in a branding repo may have **optional** translation overrides.
|
|
101
|
+
Each `service` in a branding repo may have **optional** translation overrides. These are consumed by the V6 service (so not consumed by the header/footer)
|
|
83
102
|
|
|
84
103
|
These live at `<root>/services/<SERVICE_NAME>/public/translation.json`, e.g. https://github.com/wiley/madgex-ff6102ff-0f4b-43d1-a2c7-83b835b8dee5/blob/901e5b2295892747015bd1c9bab7ac307b4e2112/services/jobseekers-frontend/public/translation.json .
|
|
85
104
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@madgex/fert",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.3.0",
|
|
4
4
|
"description": "Tool to help build the V6 branding",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fert": "./bin/cli.js"
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"prompts": "2.4.2",
|
|
49
49
|
"rimraf": "6.1.3",
|
|
50
50
|
"sass": "1.98.0",
|
|
51
|
-
"simple-git": "3.
|
|
51
|
+
"simple-git": "3.36.0",
|
|
52
52
|
"simple-update-notifier": "2.0.0",
|
|
53
53
|
"uuid-validate": "0.0.3",
|
|
54
54
|
"vite": "7.3.2",
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
{
|
|
1
|
+
{# Populate this entry file with a Theme generated from https://header-footer-podlet.job.madgexhosting.net/storybook/
|
|
2
|
+
{{ FooterThemeColumns( <-- macro name which is globally accessible without doing an import, see Storybook for Theme's macro name
|
|
3
|
+
{
|
|
4
|
+
"macroName": "FooterThemeColumns",
|
|
5
|
+
"serviceName": "jobseekers-frontend", <-- ensure serviceName is correct for this service
|
|
6
|
+
"locale": "en",
|
|
7
|
+
"cssVarColorText": "#000",
|
|
8
|
+
...more args generated from Header Footer Podlet's Storybook
|
|
9
|
+
}
|
|
10
|
+
)}}
|
|
11
|
+
#}
|
|
2
12
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
</div>
|
|
11
|
-
</div>
|
|
12
|
-
<div class="wrapper content">
|
|
13
|
-
{% include templatePath + './includes/footer-nav.njk' %}
|
|
14
|
-
|
|
15
|
-
<p class="copyright no-margin">
|
|
16
|
-
© 2008 - {{ dayjs().format('YYYY') }} bigworkbag.com, all rights reserved. Powered by Madgex Job Board Software
|
|
17
|
-
</p>
|
|
18
|
-
</div>
|
|
19
|
-
</footer>
|
|
20
|
-
</div>
|
|
13
|
+
{#
|
|
14
|
+
Alternatively, if this client has pushed for a Custom non-Theme design, you can run `npx @madgex/fert init-template` from the root of this branding repo and follow the prompts.
|
|
15
|
+
This will clone a custom version of one of the Themes from header-footer-podlet which you can modify completely.
|
|
16
|
+
|
|
17
|
+
🚨🚨🚨 Remember Custom non-Theme designs are discourage and should only happen if the client really pushed for it.
|
|
18
|
+
If there are only minor differences from a Theme and the design, then pushed back and get back into a standard Theme 🚨🚨🚨
|
|
19
|
+
#}
|
|
@@ -1,74 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
{
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
</mdgx-togglable-nav>
|
|
21
|
-
</li>
|
|
22
|
-
|
|
23
|
-
<li class="nav-toggles__item nav-toggles__item--secondary">
|
|
24
|
-
<mdgx-togglable-nav aria-controls="secondary-nav">
|
|
25
|
-
<a href="#secondary-nav" title="User menu" class="js-togglable-nav-trigger">
|
|
26
|
-
<i class="icon-after" data-icon="☺" aria-hidden="true"></i>
|
|
27
|
-
<span class="hidden">{{ translations.header["skip-to-user-menu"] }}</span>
|
|
28
|
-
</a>
|
|
29
|
-
</mdgx-togglable-nav>
|
|
30
|
-
</li>
|
|
31
|
-
</ul>
|
|
32
|
-
|
|
33
|
-
<nav aria-label="{{ translations.header["user"] }}" id="secondary-nav" class="secondary-nav togglable-nav cf js-togglable-nav">
|
|
34
|
-
<ul class="secondary-nav__items togglable-nav__items cf">
|
|
35
|
-
<li class="togglable-nav__item secondary-nav__item secondary-nav--jobseekers jobseekers jobseekers-nav">
|
|
36
|
-
{# User Nav #}
|
|
37
|
-
{%- ifAsync auth.isAuthenticated %}
|
|
38
|
-
{% include templatePath + './includes/user-nav/authenticated.njk' %}
|
|
39
|
-
{%- else %}
|
|
40
|
-
{% include templatePath + './includes/user-nav/unauthenticated.njk' %}
|
|
41
|
-
{% endif -%}
|
|
42
|
-
</li>
|
|
43
|
-
<li class="togglable-nav__item secondary-nav__item secondary-nav--recruiters recruiters recruiters-nav">
|
|
44
|
-
<ul class="recruiters__items cf">
|
|
45
|
-
<li class="togglable-nav__item recruiters__item recruiters__item--button">
|
|
46
|
-
<a
|
|
47
|
-
href="https://bigworkbag-rs.madgexjb.com/" {# Add recruiter site link var #}
|
|
48
|
-
data-icon="▶"
|
|
49
|
-
class="button button--brand icon-after icon-white js-recruiter-link recruiters__item--link">{{ translations.header["recruiters"] }}
|
|
50
|
-
</a>
|
|
51
|
-
</li>
|
|
52
|
-
</ul>
|
|
53
|
-
</li>
|
|
54
|
-
</ul>
|
|
55
|
-
</nav>
|
|
56
|
-
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
<div id="ad-mobileleaderboard" class="ad--mobileleaderboard ad--mobileleaderboard--empty"></div>
|
|
60
|
-
|
|
61
|
-
<div class="wrapper cf">
|
|
62
|
-
<div class="primary-logo">
|
|
63
|
-
<a href="/">
|
|
64
|
-
<img src="/public/images/logo.png" alt="Bigworkbag Home"/>
|
|
65
|
-
</a>
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
{% include templatePath + './includes/main-nav.njk' %}
|
|
70
|
-
|
|
71
|
-
</header>
|
|
72
|
-
</div>
|
|
73
|
-
|
|
74
|
-
<!-- END HEADER FRAGMENT -->
|
|
1
|
+
{# Populate this entry file with a Theme generated from https://header-footer-podlet.job.madgexhosting.net/storybook/
|
|
2
|
+
{{ HeaderThemeInlineUserNav( <-- macro name which is globally accessible without doing an import, see Storybook for Theme's macro name
|
|
3
|
+
{
|
|
4
|
+
"macroName": "HeaderThemeInlineUserNav",
|
|
5
|
+
"serviceName": "jobseekers-frontend", <-- ensure serviceName is correct for this service
|
|
6
|
+
"locale": "en",
|
|
7
|
+
"cssVarColorText": "#000",
|
|
8
|
+
...more args generated from Header Footer Podlet's Storybook
|
|
9
|
+
}
|
|
10
|
+
)}}
|
|
11
|
+
#}
|
|
12
|
+
|
|
13
|
+
{#
|
|
14
|
+
Alternatively, if this client has pushed for a Custom non-Theme design, you can run `npx @madgex/fert init-template` from the root of this branding repo and follow the prompts.
|
|
15
|
+
This will clone a custom version of one of the Themes from header-footer-podlet which you can modify completely.
|
|
16
|
+
|
|
17
|
+
🚨🚨🚨 Remember Custom non-Theme designs are discourage and should only happen if the client really pushed for it.
|
|
18
|
+
If there are only minor differences from a Theme and the design, then pushed back and get back into a standard Theme 🚨🚨🚨
|
|
19
|
+
#}
|