2022-11-14 08:17:33 -05:00
|
|
|
// Creates data for localizedPlanPricing object in settings.overrides.saas.js
|
|
|
|
// and plans object in main/plans.js
|
|
|
|
|
2024-10-24 07:14:00 -04:00
|
|
|
// https://github.com/import-js/eslint-plugin-import/issues/1810
|
|
|
|
// eslint-disable-next-line import/no-unresolved
|
|
|
|
import * as csv from 'csv/sync'
|
2024-11-04 04:09:11 -05:00
|
|
|
import fs from 'node:fs'
|
|
|
|
import path from 'node:path'
|
2024-10-24 07:14:00 -04:00
|
|
|
import minimist from 'minimist'
|
2024-11-04 04:09:11 -05:00
|
|
|
import { fileURLToPath } from 'node:url'
|
2024-10-24 07:14:00 -04:00
|
|
|
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
2022-11-14 08:17:33 -05:00
|
|
|
|
2023-04-26 09:31:10 -04:00
|
|
|
function readCSVFile(fileName) {
|
|
|
|
// Pick the csv file
|
|
|
|
const filePath = path.resolve(__dirname, fileName)
|
|
|
|
const input = fs.readFileSync(filePath, 'utf8')
|
|
|
|
const rawRecords = csv.parse(input, { columns: true })
|
|
|
|
return rawRecords
|
|
|
|
}
|
|
|
|
|
|
|
|
function readJSONFile(fileName) {
|
|
|
|
const filePath = path.resolve(__dirname, fileName)
|
|
|
|
const file = fs.readFileSync(filePath)
|
|
|
|
const plans = JSON.parse(file)
|
|
|
|
// convert the plans JSON from recurly to an array of
|
|
|
|
// objects matching the spreadsheet format
|
|
|
|
const result = []
|
|
|
|
for (const plan of plans) {
|
|
|
|
const newRow = { plan_code: plan.code }
|
|
|
|
for (const price of plan.currencies) {
|
|
|
|
newRow[price.currency] = price.unitAmount
|
|
|
|
}
|
|
|
|
result.push(newRow)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
2022-11-14 08:17:33 -05:00
|
|
|
|
|
|
|
// Mapping of [output_keys]:[actual_keys]
|
|
|
|
const plansMap = {
|
|
|
|
student: 'student',
|
|
|
|
personal: 'paid-personal',
|
|
|
|
collaborator: 'collaborator',
|
|
|
|
professional: 'professional',
|
|
|
|
}
|
|
|
|
|
2024-04-18 04:13:51 -04:00
|
|
|
const currencies = [
|
|
|
|
'AUD',
|
|
|
|
'BRL',
|
|
|
|
'CAD',
|
|
|
|
'CHF',
|
|
|
|
'CLP',
|
|
|
|
'COP',
|
|
|
|
'DKK',
|
|
|
|
'EUR',
|
|
|
|
'GBP',
|
|
|
|
'INR',
|
|
|
|
'MXN',
|
|
|
|
'NOK',
|
|
|
|
'NZD',
|
|
|
|
'PEN',
|
|
|
|
'SEK',
|
|
|
|
'SGD',
|
|
|
|
'USD',
|
|
|
|
]
|
2022-11-14 08:17:33 -05:00
|
|
|
|
2023-04-26 09:31:10 -04:00
|
|
|
function generatePlans(workSheetJSON) {
|
|
|
|
// localizedPlanPricing object for settings.overrides.saas.js
|
|
|
|
const localizedPlanPricing = {}
|
|
|
|
// plans object for main/plans.js
|
2022-11-14 08:17:33 -05:00
|
|
|
|
2024-04-18 04:13:51 -04:00
|
|
|
for (const currency of currencies) {
|
2023-04-26 09:31:10 -04:00
|
|
|
localizedPlanPricing[currency] = {
|
|
|
|
free: {
|
2024-04-18 04:13:51 -04:00
|
|
|
monthly: 0,
|
|
|
|
annual: 0,
|
2023-04-26 09:31:10 -04:00
|
|
|
},
|
|
|
|
}
|
2022-11-14 08:17:33 -05:00
|
|
|
|
2023-04-26 09:31:10 -04:00
|
|
|
for (const [outputKey, actualKey] of Object.entries(plansMap)) {
|
|
|
|
const monthlyPlan = workSheetJSON.find(
|
|
|
|
data => data.plan_code === actualKey
|
|
|
|
)
|
2022-11-14 08:17:33 -05:00
|
|
|
|
2023-04-26 09:31:10 -04:00
|
|
|
if (!monthlyPlan) throw new Error(`Missing plan: ${actualKey}`)
|
2023-07-31 03:34:34 -04:00
|
|
|
if (!(currency in monthlyPlan))
|
|
|
|
throw new Error(
|
|
|
|
`Missing currency "${currency}" for plan "${actualKey}"`
|
|
|
|
)
|
2022-11-14 08:17:33 -05:00
|
|
|
|
2023-04-26 09:31:10 -04:00
|
|
|
const actualKeyAnnual = `${actualKey}-annual`
|
|
|
|
const annualPlan = workSheetJSON.find(
|
|
|
|
data => data.plan_code === actualKeyAnnual
|
|
|
|
)
|
2022-11-14 08:17:33 -05:00
|
|
|
|
2023-04-26 09:31:10 -04:00
|
|
|
if (!annualPlan) throw new Error(`Missing plan: ${actualKeyAnnual}`)
|
2023-07-31 03:34:34 -04:00
|
|
|
if (!(currency in annualPlan))
|
|
|
|
throw new Error(
|
|
|
|
`Missing currency "${currency}" for plan "${actualKeyAnnual}"`
|
|
|
|
)
|
2022-11-14 08:17:33 -05:00
|
|
|
|
2024-04-18 04:13:51 -04:00
|
|
|
const monthly = Number(monthlyPlan[currency])
|
|
|
|
const monthlyTimesTwelve = Number(monthlyPlan[currency] * 12)
|
|
|
|
const annual = Number(annualPlan[currency])
|
2023-04-26 09:31:10 -04:00
|
|
|
|
|
|
|
localizedPlanPricing[currency] = {
|
|
|
|
...localizedPlanPricing[currency],
|
|
|
|
[outputKey]: { monthly, monthlyTimesTwelve, annual },
|
|
|
|
}
|
2022-11-14 08:17:33 -05:00
|
|
|
}
|
2023-04-26 09:31:10 -04:00
|
|
|
}
|
2024-04-18 04:13:51 -04:00
|
|
|
return { localizedPlanPricing }
|
2023-04-26 09:31:10 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function generateGroupPlans(workSheetJSON) {
|
|
|
|
const groupPlans = workSheetJSON.filter(data =>
|
|
|
|
data.plan_code.startsWith('group')
|
|
|
|
)
|
|
|
|
|
|
|
|
const sizes = ['2', '3', '4', '5', '10', '20', '50']
|
|
|
|
|
|
|
|
const result = {}
|
|
|
|
for (const type1 of ['educational', 'enterprise']) {
|
|
|
|
result[type1] = {}
|
|
|
|
for (const type2 of ['professional', 'collaborator']) {
|
|
|
|
result[type1][type2] = {}
|
|
|
|
for (const currency of currencies) {
|
|
|
|
result[type1][type2][currency] = {}
|
|
|
|
for (const size of sizes) {
|
|
|
|
const planCode = `group_${type2}_${size}_${type1}`
|
|
|
|
const plan = groupPlans.find(data => data.plan_code === planCode)
|
|
|
|
|
|
|
|
if (!plan) throw new Error(`Missing plan: ${planCode}`)
|
|
|
|
|
|
|
|
result[type1][type2][currency][size] = {
|
|
|
|
price_in_cents: plan[currency] * 100,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-11-14 08:17:33 -05:00
|
|
|
}
|
|
|
|
}
|
2023-04-26 09:31:10 -04:00
|
|
|
return result
|
2022-11-14 08:17:33 -05:00
|
|
|
}
|
|
|
|
|
2023-04-26 09:31:10 -04:00
|
|
|
const argv = minimist(process.argv.slice(2), {
|
2023-07-31 03:34:34 -04:00
|
|
|
string: ['output', 'file'],
|
|
|
|
alias: { o: 'output', f: 'file' },
|
2023-04-26 09:31:10 -04:00
|
|
|
})
|
2022-11-14 08:17:33 -05:00
|
|
|
|
2023-04-26 09:31:10 -04:00
|
|
|
let input
|
|
|
|
if (argv.file) {
|
|
|
|
const ext = path.extname(argv.file)
|
|
|
|
switch (ext) {
|
|
|
|
case '.csv':
|
|
|
|
input = readCSVFile(argv.file)
|
|
|
|
break
|
|
|
|
case '.json':
|
|
|
|
input = readJSONFile(argv.file)
|
|
|
|
break
|
|
|
|
default:
|
2023-07-31 03:34:34 -04:00
|
|
|
console.log('Invalid file type: must be csv or json')
|
2023-04-26 09:31:10 -04:00
|
|
|
}
|
|
|
|
} else {
|
2024-10-24 07:14:00 -04:00
|
|
|
console.log('usage: node plans.mjs -f <file.csv|file.json> -o <dir>')
|
2023-04-26 09:31:10 -04:00
|
|
|
process.exit(1)
|
|
|
|
}
|
|
|
|
// removes quotes from object keys
|
|
|
|
const formatJS = obj =>
|
|
|
|
JSON.stringify(obj, null, 2).replace(/"([^"]+)":/g, '$1:')
|
|
|
|
const formatJSON = obj => JSON.stringify(obj, null, 2)
|
2023-07-31 03:34:34 -04:00
|
|
|
|
2023-04-26 09:31:10 -04:00
|
|
|
function writeFile(outputFile, data) {
|
|
|
|
console.log(`Writing ${outputFile}`)
|
|
|
|
fs.writeFileSync(outputFile, data)
|
|
|
|
}
|
2023-07-31 03:34:34 -04:00
|
|
|
|
2024-04-18 04:13:51 -04:00
|
|
|
const { localizedPlanPricing } = generatePlans(input)
|
2023-04-26 09:31:10 -04:00
|
|
|
const groupPlans = generateGroupPlans(input)
|
2022-11-14 08:17:33 -05:00
|
|
|
|
2023-04-26 09:31:10 -04:00
|
|
|
if (argv.output) {
|
|
|
|
const dir = argv.output
|
|
|
|
// check if output directory exists
|
|
|
|
if (!fs.existsSync(dir)) {
|
|
|
|
console.log(`Creating output directory ${dir}`)
|
|
|
|
fs.mkdirSync(dir)
|
|
|
|
}
|
|
|
|
// check if output directory is a directory and report error if not
|
|
|
|
if (!fs.lstatSync(dir).isDirectory()) {
|
|
|
|
console.error(`Error: output dir ${dir} is not a directory`)
|
|
|
|
process.exit(1)
|
|
|
|
}
|
|
|
|
writeFile(`${dir}/localizedPlanPricing.json`, formatJS(localizedPlanPricing))
|
|
|
|
writeFile(`${dir}/groups.json`, formatJSON(groupPlans))
|
|
|
|
} else {
|
|
|
|
console.log('LOCALIZED', localizedPlanPricing)
|
|
|
|
console.log('GROUP PLANS', JSON.stringify(groupPlans, null, 2))
|
2022-11-14 08:17:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
console.log('Completed!')
|