diff --git a/.gitignore b/.gitignore index b21803f..1999e01 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,30 @@ dmypy.json #Intillij .idea/ +# Config +js-scripts/update-ff/*.json + +# Node.js +node_modules/ + +# Logs +logs +*.log +npm-debug.log* + +# Dependency directories +pids +logs +*.pid +*.seed +*.pid.lock + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + diff --git a/js-scripts/dot-config.js b/js-scripts/dot-config.js new file mode 100644 index 0000000..1fc901b --- /dev/null +++ b/js-scripts/dot-config.js @@ -0,0 +1,20 @@ +// This can be env vars configured by the developer +export const DOTCMS_HOST = 'http://localhost:8080'; +export const DOTCMS_USER = { + username: 'admin@dotcms.com', + password: 'admin' +}; + +// API Endpoints +export const DOTCMS_SYSTEM_TABLE_API = `${DOTCMS_HOST}/api/v1/system-table`; +export const DOTCMS_TOKEN_API = `${DOTCMS_HOST}/api/v1/authentication/api-token`; +export const DOTCMS_EMA_CONFIG_API = `${DOTCMS_HOST}/api/v1/apps/dotema-config-v2/`; +export const DOTCMS_EXP_CONFIG_API = `${DOTCMS_HOST}/api/v1/apps/dotAnalytics-config/`; +export const DOTCMS_SITES_API = `${DOTCMS_HOST}/api/v1/site?filter=*&per_page=15&archive=false`; +export const DOTCMS_COMPANY_INFO_API = `${DOTCMS_HOST}/api/config/saveCompanyBasicInfo`; + +// Common Constants +export const BTOA_USER = btoa(`${DOTCMS_USER.username}:${DOTCMS_USER.password}`); +export const BASIC_AUTH = `Basic ${BTOA_USER}`; +export const DOTCMS_JVM_INFO_API = `${DOTCMS_HOST}/api/v1/jvm`; +export const DEMO_SITE_HOSTNAME = 'demo.dotcms.com'; diff --git a/js-scripts/get-docker-image/get-docker-image.js b/js-scripts/get-docker-image/get-docker-image.js new file mode 100644 index 0000000..372c788 --- /dev/null +++ b/js-scripts/get-docker-image/get-docker-image.js @@ -0,0 +1,20 @@ +import { BASIC_AUTH, DOTCMS_JVM_INFO_API } from '../dot-config.js'; + +const DOCKER_TAG = 'dotcms/dotcms:trunk_'; + +// Just get the current Docker Image +fetch(DOTCMS_JVM_INFO_API, { + method: 'GET', + headers: { + accept: '*/*', + Authorization: BASIC_AUTH + } +}).then((response) => { + if (response.ok) { + response.json().then((data) => { + console.log(`DotCMS Docker Image: [${DOCKER_TAG + data.release.buildNumber}]`); + }); + } else { + console.log('Something occured while fetching the JVM information'); + } +}); diff --git a/js-scripts/package-lock.json b/js-scripts/package-lock.json new file mode 100644 index 0000000..37d90ab --- /dev/null +++ b/js-scripts/package-lock.json @@ -0,0 +1,44 @@ +{ + "name": "scripts", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "scripts", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "prompts": "^2.4.2" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + } + } +} diff --git a/js-scripts/package.json b/js-scripts/package.json new file mode 100644 index 0000000..d52871b --- /dev/null +++ b/js-scripts/package.json @@ -0,0 +1,14 @@ +{ + "name": "scripts", + "version": "1.0.0", + "description": "", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "prompts": "^2.4.2" + } +} diff --git a/js-scripts/update-colors/update-colors.js b/js-scripts/update-colors/update-colors.js new file mode 100644 index 0000000..17388bf --- /dev/null +++ b/js-scripts/update-colors/update-colors.js @@ -0,0 +1,40 @@ +import { BASIC_AUTH, DOTCMS_COMPANY_INFO_API } from '../dot-config.js'; +import queryString from 'querystring'; + +const primaryColor = process.argv[2] ?? '#426BF0'; // Fallback to design system color +const secondaryColor = process.argv[3] ?? '#7042F0'; // Fallback to design system color + +// This is the data that is for default in this endpoint, I just changed the colors +const formData = { + portalURL: 'localhost', + mx: '', + emailAddress: 'dotCMS Website ', + size: '#1b3359', + type: primaryColor, + street: secondaryColor, + homeURL: '/html/images/backgrounds/bg-11.jpg', + city: '/dA/bc66ae086e242991d89e386d353c7529/asset/dotCMS-400x200.png' +}; + +const body = `portalURL=${queryString.encode(formData)}`; // We need to encode the object to send it as a string + +fetch(DOTCMS_COMPANY_INFO_API, { + headers: { + accept: '*/*', + Authorization: BASIC_AUTH, + 'content-type': 'application/x-www-form-urlencoded' + }, + body, + method: 'POST' +}).then((response) => { + if (response.ok) { + response.text().then(() => { + console.log('Colors updated\n', { + primaryColor, + secondaryColor + }); + }); + } else { + console.log('Something occured while updating the colors'); + } +}); diff --git a/js-scripts/update-ema-config/update-ema-config.js b/js-scripts/update-ema-config/update-ema-config.js new file mode 100644 index 0000000..d164891 --- /dev/null +++ b/js-scripts/update-ema-config/update-ema-config.js @@ -0,0 +1,36 @@ +import { DOTCMS_EMA_CONFIG_API, BASIC_AUTH } from '../dot-config.js'; +import { getDemoSite } from '../utils.js'; + +const projectURL = process.argv[2] ?? 'http://localhost:3000'; // Fallback to localhost:3000 which is NextJS + +const emaConfig = { + // Basic config to test UVE Headless + config: [ + { + pattern: '.*', + url: projectURL + } + ] +}; + +const stringEmaConfig = JSON.stringify(emaConfig); + +const demoSite = await getDemoSite(); + +fetch(`${DOTCMS_EMA_CONFIG_API}${demoSite.identifier}`, { + method: 'POST', + headers: { + Authorization: BASIC_AUTH, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + configuration: { + hidden: false, + value: stringEmaConfig + } + }) +}).then((response) => { + response.json().then((data) => { + if (data.entity === 'Ok') console.log('EMA Config updated'); + }); +}); diff --git a/js-scripts/update-exp-config/update-exp-config.js b/js-scripts/update-exp-config/update-exp-config.js new file mode 100644 index 0000000..1be419f --- /dev/null +++ b/js-scripts/update-exp-config/update-exp-config.js @@ -0,0 +1,41 @@ +import { DOTCMS_EXP_CONFIG_API, BASIC_AUTH } from '../dot-config.js'; +import { getDemoSite } from '../utils.js'; + +// This is the config for testing experiments locally +const expConfig = { + clientId: { + hidden: false, + value: 'analytics-customer-customer1' + }, + clientSecret: { + hidden: true, + value: 'testsecret' + }, + analyticsConfigUrl: { + hidden: false, + value: 'http://host.docker.internal:8088/c/customer1/cluster1/keys' + }, + analyticsWriteUrl: { + hidden: false, + value: 'http://host.docker.internal:8081/api/v1/event' + }, + analyticsReadUrl: { + hidden: false, + value: 'http://host.docker.internal:4001/' + } +}; + +const demoSite = await getDemoSite(); + +fetch(`${DOTCMS_EXP_CONFIG_API}${demoSite.identifier}`, { + method: 'POST', + headers: { + Authorization: BASIC_AUTH, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(expConfig) +}).then((response) => { + response.json().then((data) => { + if (data.entity === 'Ok') console.log('Experiments Config updated'); + }); +}); diff --git a/js-scripts/update-ff/update-ff.js b/js-scripts/update-ff/update-ff.js new file mode 100644 index 0000000..ab2dd3d --- /dev/null +++ b/js-scripts/update-ff/update-ff.js @@ -0,0 +1,149 @@ +// First of all, you can see in this script how there is a default configuration that we will use to create the config file +// Feel free to add more flags or change the default values before you run this script. +// This script will create a config file with the default values if it doesn't exist. +// Then it will prompt you whether you want to update the system table with new flags or not. +// If you choose to update the system table, it will prompt you to enable or disable the flags. +// After you choose the flags, it will update the system table with the new values and save the new config file. +// If you choose not to update the system table, it will update the system table with the current values. + +// To make this script work, you need to delete all env variables you will use here to set the flags and run the script. +// This is because dotCMS system table is the last source of truth for the flags, so if you have env variables set, the system table will not be checked. +// Also you have to install the "prompts" package. + +// In the future we will have a frontend for this, but for now, we can use this to update the flags easily. + +import prompts from 'prompts'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { DOTCMS_SYSTEM_TABLE_API, BASIC_AUTH } from '../dot-config.js'; + +const __filename = fileURLToPath(import.meta.url); + +const __dirname = path.dirname(__filename); + +const configFile = path.join(__dirname, 'config.json'); + +let flags; // Our flags will be stored here after we read the config file + +// This is the default configuration that we will use to create the config file +// Feel free to add more flags or change the default values before you run this script +const DEFAULT_ENV_CONFIG = { + DOT_FEATURE_FLAG_EXPERIMENTS: { value: 'true', title: 'Enable Experiments' }, + DOT_DOTCMS_DEV_MODE: { value: 'true', title: 'Enable Dev Mode' }, + DOT_ENABLE_EXPERIMENTS_AUTO_JS_INJECTION: { + value: 'true', + title: 'Enable Experiments Auto JS Injection' // The title is what you will see in the prompt when you run the script + }, + DOT_FEATURE_FLAG_SEO_IMPROVEMENTS: { value: 'true', title: 'Enable SEO Improvements' }, + DOT_FEATURE_FLAG_SEO_PAGE_TOOLS: { value: 'true', title: 'Enable SEO Page Tools' }, + DOT_FEATURE_FLAG_NEW_BINARY_FIELD: { value: 'true', title: 'Enable New Binary Field' }, + DOT_CONTENT_EDITOR2_ENABLED: { value: 'true', title: 'Enable new Edit Content' }, + FEATURE_FLAG_EDIT_URL_CONTENT_MAP: { + value: 'true', + title: 'Enable edit UrlContentMap from Edit Page' + }, + FEATURE_FLAG_NEW_EDIT_PAGE: { + value: 'true', + title: 'Enable new Edit Page' + } +}; + +function readConfig() { + try { + flags = JSON.parse(fs.readFileSync(configFile, 'utf8')); + } catch (error) { + console.log(error); + } +} + +function postFeatureFlags(key, value) { + return fetch(DOTCMS_SYSTEM_TABLE_API, { + method: 'POST', + headers: { + Authorization: BASIC_AUTH, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + key, + value + }) + }); +} + +function exectUpdate(flags = flags) { + Object.keys(flags).forEach((key) => { + postFeatureFlags(key, flags[key].value).then((res) => { + res.json() + .then((data) => { + const color = flags[key].value == 'true' ? '\x1b[32m' : '\x1b[31m'; + + console.log('\x1b[35m%s\x1b[0m', data.entity); + console.log(`\x1b[${color}%s\x1b[0m`, `with value: ${flags[key].value}\n`); + }) + .catch((err) => { + console.log(err); + }); + }); + }); +} + +async function runPrompts() { + const FF_KEYS = flags ? Object.keys(flags) : Object.keys(DEFAULT_ENV_CONFIG); + + const { value } = await prompts({ + type: 'confirm', + name: 'value', + message: 'Do you want to update the system table with new flags?', + initial: false + }); + + if (!value) { + // We execute the update with the current values + exectUpdate(flags); + } else { + const choices = FF_KEYS.map((key) => { + return { + title: flags[key].title, + value: key, + selected: flags[key].value == 'true' + }; + }); + + const { value } = await prompts({ + type: 'multiselect', + name: 'value', + message: 'Enable Feature Flags', + hint: '- Space to select. Return to submit', + choices + }); // This values are the keys that are on. + + const newJSON = FF_KEYS.reduce((acc, key) => { + acc[key] = { + ...flags[key], + value: value.includes(key).toString() + }; + + return acc; + }, {}); + + exectUpdate(newJSON); + fs.writeFile(configFile, JSON.stringify(newJSON, null, 2), { flag: 'w' }, (err) => { + if (err) throw err; + + console.log('\x1b[33m%s\x1b[0m', 'The new config has been saved!\n'); + }); + } +} + +// Create the config file with the default values if it doesn't exist +fs.writeFile(configFile, JSON.stringify(DEFAULT_ENV_CONFIG, null, 2), { flag: 'wx' }, (error) => { + if (error) { + console.log('We already have a config file!'); + readConfig(); + } else { + console.log('Configuration file created with initial values!'); + } + + runPrompts(); +}); diff --git a/js-scripts/update-token-ema/update-token-ema.js b/js-scripts/update-token-ema/update-token-ema.js new file mode 100644 index 0000000..593e54c --- /dev/null +++ b/js-scripts/update-token-ema/update-token-ema.js @@ -0,0 +1,89 @@ +import fs from 'fs'; +import { DOTCMS_USER, DOTCMS_TOKEN_API } from '../dot-config.js'; + +const DOTCMS_PATH = '/Users/zjaaal/Desktop/repos/dotcms'; // Change this to your dotcms path + +const label = (Math.random() + 1).toString(36).substring(7); // This generates a random string to label the token + +const project = process.argv[2] ?? 'nextjs'; // You can pass an argument to specify the project (probably in the future we will have vue, astro, etc). if not it will default to nextjs + +// Now that we are introducing new technologies to ema, we can have different configurations for each project +const configByProject = { + nextjs: { + tokenLabel: 'NEXT_PUBLIC_DOTCMS_AUTH_TOKEN', + path: `${DOTCMS_PATH}/core/examples/nextjs/.env.local`, + regex: 'NEXT_PUBLIC_DOTCMS_AUTH_TOKEN=.*', + quotes: false, + separator: '=' + }, + astro: { + tokenLabel: 'PUBLIC_DOTCMS_AUTH_TOKEN', + path: `${DOTCMS_PATH}/core/examples/astro/.env.local`, + regex: 'PUBLIC_DOTCMS_AUTH_TOKEN=.*', + quotes: false, + separator: '=' + }, + vuejs: { + tokenLabel: 'VITE_DOTCMS_TOKEN', + path: `${DOTCMS_PATH}/core/examples/vuejs/.env.local`, + regex: 'VITE_DOTCMS_TOKEN=.*', + quotes: false, + separator: '=' + }, + angular: { + tokenLabel: 'authToken', + path: `${DOTCMS_PATH}/core/examples/angular/src/environments/environment.development.ts`, + regex: 'authToken:.*', + quotes: true, + separator: ':' + } +}; + +fetch(DOTCMS_TOKEN_API, { + method: 'POST', + headers: { + accept: 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + user: DOTCMS_USER.username, + password: DOTCMS_USER.password, + expirationDays: 30, + label + }) +}) + .then((response) => response.json()) + .then((data) => { + const token = data.entity.token; // This is the token that we will use to authenticate the requests + + const { tokenLabel, path, regex, quotes, separator } = configByProject[project]; + // Read the file + fs.readFile(path, 'utf8', (err, data) => { + if (err) { + console.error(err); + return; + } + + const tokenRegex = new RegExp(regex); + + const tokenReplacement = quotes + ? `${tokenLabel}${separator}"${token}",` + : `${tokenLabel}${separator}${token}`; + + // Find the token + const result = data.replace(tokenRegex, tokenReplacement); + + // Write the file with the new token + fs.writeFile(path, result, 'utf8', (err) => { + if (err) { + console.error(err); + return; + } + + console.log('Token updated'); + }); + }); + }) + .catch((error) => { + console.error('Error:', error); + }); diff --git a/js-scripts/utils.js b/js-scripts/utils.js new file mode 100644 index 0000000..064352e --- /dev/null +++ b/js-scripts/utils.js @@ -0,0 +1,14 @@ +import { DOTCMS_SITES_API, BASIC_AUTH, DEMO_SITE_HOSTNAME } from './dot-config.js'; + +export async function getDemoSite() { + const response = await fetch(DOTCMS_SITES_API, { + method: 'GET', + headers: { + Authorization: BASIC_AUTH + } + }); + + const data = await response.json(); + + return data.entity.find((site) => site.hostname === DEMO_SITE_HOSTNAME); +}