{"version":3,"sources":["jsdelivr-header.js","/npm/sla-wizard-nginx-confd@1.0.4/index.js"],"names":[],"mappings":"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","file":"/npm/sla-wizard-nginx-confd@1.0.4/index.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.48.0.\n * Original file: /npm/sla-wizard-nginx-confd@1.0.4/index.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","// plugins/nginx-confd.js\nconst fs = require(\"fs\");\nconst path = require(\"path\");\n\n/**\n * Plugin that generates nginx configuration split into:\n * - Main nginx.conf with server block structure (includes http-level directives)\n * - Individual conf.d/<user>.conf files with location blocks only\n *\n * @param {Object} program - Commander program instance\n * @param {Object} ctx - Context with utils and generate functions\n */\nfunction apply(program, ctx) {\n  program\n    .command(\"config-nginx-confd\")\n    .description(\n      \"Generate nginx configuration split into main file and conf.d directory\",\n    )\n    .requiredOption(\n      \"-o, --outDir <outputDirectory>\",\n      \"Output directory for nginx.conf and conf.d/\",\n    )\n    .option(\n      \"--sla <slaPath>\",\n      \"One of: 1) single SLA, 2) folder of SLAs, 3) URL returning an array of SLA objects\",\n      \"./specs/sla.yaml\",\n    )\n    .option(\"--oas <pathToOAS>\", \"Path to an OAS v3 file.\", \"./specs/oas.yaml\")\n    .option(\n      \"--customTemplate <customTemplate>\",\n      \"Custom proxy configuration template.\",\n    )\n    .option(\n      \"--authLocation <authLocation>\",\n      \"Where to look for the authentication parameter. Must be one of: header, query, url.\",\n      \"header\",\n    )\n    .option(\n      \"--authName <authName>\",\n      'Name of the authentication parameter, such as \"token\" or \"apikey\".',\n      \"apikey\",\n    )\n    .option(\"--proxyPort <proxyPort>\", \"Port on which the proxy is running\", 80)\n    .action(function (options) {\n      configNginxConfd(options, ctx);\n    });\n\n  program\n    .command(\"add-to-confd\")\n    .description(\n      \"Generate configuration files for conf.d directory without creating nginx.conf\",\n    )\n    .requiredOption(\n      \"-o, --outDir <outputDirectory>\",\n      \"Output directory for conf.d/\",\n    )\n    .option(\n      \"--sla <slaPath>\",\n      \"One of: 1) single SLA, 2) folder of SLAs, 3) URL returning an array of SLA objects\",\n      \"./specs/sla.yaml\",\n    )\n    .option(\"--oas <pathToOAS>\", \"Path to an OAS v3 file.\", \"./specs/oas.yaml\")\n    .option(\n      \"--customTemplate <customTemplate>\",\n      \"Custom proxy configuration template.\",\n    )\n    .option(\n      \"--authLocation <authLocation>\",\n      \"Where to look for the authentication parameter. Must be one of: header, query, url.\",\n      \"header\",\n    )\n    .option(\n      \"--authName <authName>\",\n      'Name of the authentication parameter, such as \"token\" or \"apikey\".',\n      \"apikey\",\n    )\n    .option(\"--proxyPort <proxyPort>\", \"Port on which the proxy is running\", 80)\n    .action(function (options) {\n      addToConfd(options, ctx);\n    });\n\n  program\n    .command(\"remove-from-confd\")\n    .description(\n      \"Remove configuration files from conf.d directory based on an SLA\",\n    )\n    .requiredOption(\n      \"-o, --outDir <outputDirectory>\",\n      \"Output directory containing conf.d/\",\n    )\n    .requiredOption(\n      \"--sla <slaPath>\",\n      \"Path to the SLA file(s) or directory to remove\",\n    )\n    .action(function (options) {\n      removeFromConfd(options, ctx);\n    });\n}\n\n/**\n * Programmatic logic for config-nginx-confd\n */\nfunction configNginxConfd(options, ctx) {\n  const { utils, generate } = ctx;\n  options = utils.validateParamsCLI(\"nginx\", options);\n\n  const tempFile = path.join(options.outDir, \".temp-nginx-full.conf\");\n\n  if (!fs.existsSync(options.outDir)) {\n    fs.mkdirSync(options.outDir, { recursive: true });\n  }\n\n  generate.generateConfigHandle(\n    options.oas || \"./specs/oas.yaml\",\n    \"nginx\",\n    options.sla || \"./specs/sla.yaml\",\n    tempFile,\n    options.customTemplate,\n    options.authLocation || \"header\",\n    options.authName || \"apikey\",\n    options.proxyPort || 80,\n  );\n\n  const fullConfig = fs.readFileSync(tempFile, \"utf8\");\n  splitNginxConfig(fullConfig, options.outDir);\n  fs.unlinkSync(tempFile);\n\n  console.log(`✓ Nginx configuration generated in ${options.outDir}/`);\n  console.log(`  - nginx.conf (main configuration)`);\n  console.log(`  - conf.d/ (user-specific configurations)`);\n}\n\n/**\n * Programmatic logic for add-to-confd\n */\nfunction addToConfd(options, ctx) {\n  const { utils, generate } = ctx;\n  options = utils.validateParamsCLI(\"nginx\", options);\n\n  const tempFile = path.join(options.outDir, \".temp-nginx-full.conf\");\n\n  if (!fs.existsSync(options.outDir)) {\n    fs.mkdirSync(options.outDir, { recursive: true });\n  }\n\n  generate.generateConfigHandle(\n    options.oas || \"./specs/oas.yaml\",\n    \"nginx\",\n    options.sla || \"./specs/sla.yaml\",\n    tempFile,\n    options.customTemplate,\n    options.authLocation || \"header\",\n    options.authName || \"apikey\",\n    options.proxyPort || 80,\n  );\n\n  const fullConfig = fs.readFileSync(tempFile, \"utf8\");\n  splitNginxConfig(fullConfig, options.outDir, true);\n  fs.unlinkSync(tempFile);\n\n  console.log(`✓ Nginx configuration generated in ${options.outDir}/`);\n  console.log(`  - conf.d/ (user-specific configurations)`);\n}\n\n/**\n * Programmatic logic for remove-from-confd\n */\nfunction removeFromConfd(options, ctx) {\n  const { utils } = ctx;\n  const jsyaml = require(\"js-yaml\");\n  const confDDir = path.join(options.outDir, \"conf.d\");\n\n  if (!fs.existsSync(confDDir)) {\n    console.error(`Error: conf.d directory not found in ${options.outDir}`);\n    return;\n  }\n\n  let SLAs = [];\n  try {\n    if (fs.lstatSync(options.sla).isDirectory()) {\n      fs.readdirSync(options.sla).forEach((file) => {\n        if (\n          file.endsWith(\".yaml\") ||\n          file.endsWith(\".yml\") ||\n          file.endsWith(\".json\")\n        ) {\n          const content = fs.readFileSync(path.join(options.sla, file), \"utf8\");\n          SLAs.push(jsyaml.load(content));\n        }\n      });\n    } else {\n      const content = fs.readFileSync(options.sla, \"utf8\");\n      SLAs.push(jsyaml.load(content));\n    }\n  } catch (err) {\n    console.error(`Error reading SLA(s): ${err.message}`);\n    return;\n  }\n\n  const SLAsFiltered = utils.validateSLAs(SLAs);\n\n  let deletedCount = 0;\n  SLAsFiltered.forEach((sla) => {\n    const slaContextID = sla.context.id;\n    const planName = sla.plan.name;\n    const userKey = `${slaContextID}_${planName}`;\n    const userConfigFile = path.join(confDDir, `${userKey}.conf`);\n\n    if (fs.existsSync(userConfigFile)) {\n      fs.unlinkSync(userConfigFile);\n      console.log(`  - Deleted: conf.d/${userKey}.conf`);\n      deletedCount++;\n      // Also remove the user's http-level directives from nginx.conf\n      const mainConfigFile = path.join(options.outDir, \"nginx.conf\");\n      updateNginxConfRemoveUser(userKey, mainConfigFile);\n    } else {\n      console.log(`  - Not found: conf.d/${userKey}.conf`);\n    }\n  });\n\n  if (deletedCount > 0) {\n    console.log(\n      `✓ Successfully removed ${deletedCount} configuration file(s) from ${options.outDir}/conf.d/`,\n    );\n  } else {\n    console.log(`! No matching configuration files were found to remove.`);\n  }\n}\n\n/**\n * Splits the nginx configuration into main file and conf.d files\n * @param {string} fullConfig - The complete nginx configuration\n * @param {string} outDir - Output directory\n * @param {boolean} skipMainConfig - Whether to skip generating nginx.conf\n */\nfunction splitNginxConfig(fullConfig, outDir, skipMainConfig = false) {\n  const confDDir = path.join(outDir, \"conf.d\");\n\n  // Create conf.d directory\n  if (!fs.existsSync(confDDir)) {\n    fs.mkdirSync(confDDir, { recursive: true });\n  }\n\n  // Parse the configuration.\n  // limit_req_zone and map are http-context directives — they must stay in\n  // nginx.conf (mainConfig). Only location blocks go into conf.d files, which\n  // are included inside the server block.\n  const lines = fullConfig.split(\"\\n\");\n\n  const locationBlocks = [];\n  let mainConfig = [];\n\n  let inLocationBlock = false;\n  let currentBlock = [];\n  let currentBlockName = \"\";\n  let braceCount = 0;\n\n  for (let i = 0; i < lines.length; i++) {\n    const line = lines[i];\n    const trimmedLine = line.trim();\n\n    // Track location blocks\n    if (trimmedLine.startsWith(\"location /\") && !trimmedLine.startsWith(\"location ~/\")) {\n      inLocationBlock = true;\n      currentBlock = [line];\n      // Extract the location name for grouping\n      const match = trimmedLine.match(/location\\s+\\/([^\\s{]+)/);\n      if (match) {\n        currentBlockName = match[1];\n      }\n      braceCount =\n        (line.match(/{/g) || []).length - (line.match(/}/g) || []).length;\n      continue;\n    }\n\n    if (inLocationBlock) {\n      currentBlock.push(line);\n      braceCount +=\n        (line.match(/{/g) || []).length - (line.match(/}/g) || []).length;\n\n      if (braceCount === 0) {\n        locationBlocks.push({\n          name: currentBlockName,\n          content: currentBlock.join(\"\\n\"),\n        });\n        inLocationBlock = false;\n        currentBlock = [];\n        currentBlockName = \"\";\n      }\n      continue;\n    }\n\n    mainConfig.push(line);\n  }\n\n  const userGroups = {};\n\n  locationBlocks.forEach((block) => {\n    // Extract user identifier from location name\n    // Format: <context_id>_<plan>_<endpoint>_<method>\n    const parts = block.name.split(\"_\");\n    if (parts.length >= 2) {\n      const userKey = extractUserKey(block.name);\n\n      if (!userGroups[userKey]) {\n        userGroups[userKey] = {\n          locations: [],\n        };\n      }\n\n      userGroups[userKey].locations.push(block.content);\n    }\n  });\n\n  // Generate conf.d files for each user — only location blocks (server context)\n  Object.keys(userGroups).forEach((userKey) => {\n    const group = userGroups[userKey];\n    let userConfig = \"\";\n\n    // Add location blocks\n    if (group.locations.length > 0) {\n      userConfig += \"# Endpoint locations\\n\";\n      userConfig +=\n        group.locations.map((loc) => loc.trim()).join(\"\\n\\n\") + \"\\n\";\n    }\n\n    // Write user config file\n    const userConfigFile = path.join(confDDir, `${userKey}.conf`);\n    fs.writeFileSync(userConfigFile, userConfig);\n  });\n\n  // Generate main nginx.conf\n  if (!skipMainConfig) {\n    let mainConfigContent = mainConfig.join(\"\\n\");\n\n    // Add include directive before the closing server brace\n    const serverClosingIndex = mainConfigContent.lastIndexOf(\"    }\");\n    if (serverClosingIndex !== -1) {\n      const beforeClosing = mainConfigContent.substring(0, serverClosingIndex);\n      const afterClosing = mainConfigContent.substring(serverClosingIndex);\n      mainConfigContent =\n        beforeClosing +\n        \"\\n        # Include user-specific configurations\\n        include conf.d/*.conf;\\n\\n\" +\n        afterClosing;\n    }\n\n    // Write main config\n    const mainConfigFile = path.join(outDir, \"nginx.conf\");\n    fs.writeFileSync(mainConfigFile, mainConfigContent);\n  } else {\n    // add-to-confd: inject the new user's http-level directives into existing nginx.conf\n    const mainConfigFile = path.join(outDir, \"nginx.conf\");\n    updateNginxConfAddUser(fullConfig, mainConfigFile);\n  }\n}\n\n/**\n * Extract user key from location name\n * Format: <context_id>_<plan>_<endpoint>_<method>\n */\nfunction extractUserKey(locationName) {\n  const match = locationName.match(/^([^_]+)_([^_]+)_/);\n  if (match) {\n    return `${match[1]}_${match[2]}`;\n  }\n  return locationName;\n}\n\n/**\n * Updates an existing nginx.conf to inject a new user's http-level directives\n * (limit_req_zone lines and map entries) extracted from a freshly generated full config.\n * Called by add-to-confd so the new user's rate-limit zones exist in nginx.conf.\n */\nfunction updateNginxConfAddUser(fullConfig, nginxConfPath) {\n  if (!fs.existsSync(nginxConfPath)) return;\n\n  const lines = fullConfig.split(\"\\n\");\n  const newLimitReqZones = [];\n  const newMapEntries = [];\n  let inMapBlock = false;\n  let braceCount = 0;\n\n  for (const line of lines) {\n    const trimmed = line.trim();\n    if (trimmed.startsWith(\"limit_req_zone\")) {\n      newLimitReqZones.push(trimmed);\n      continue;\n    }\n    if (trimmed.startsWith(\"map $\")) {\n      inMapBlock = true;\n      braceCount = (line.match(/{/g) || []).length - (line.match(/}/g) || []).length;\n      continue;\n    }\n    if (inMapBlock) {\n      braceCount += (line.match(/{/g) || []).length - (line.match(/}/g) || []).length;\n      if (braceCount === 0) { inMapBlock = false; continue; }\n      if (trimmed && !trimmed.startsWith(\"default\")) {\n        newMapEntries.push(trimmed);\n      }\n    }\n  }\n\n  let conf = fs.readFileSync(nginxConfPath, \"utf8\");\n\n  // Inject new limit_req_zone lines before the limit_req_status directive\n  if (newLimitReqZones.length > 0) {\n    const injection = newLimitReqZones.map((z) => \"    \" + z).join(\"\\n\") + \"\\n\";\n    conf = conf.replace(/(\\s*limit_req_status\\s+429;)/, \"\\n\" + injection + \"$1\");\n  }\n\n  // Inject new map entries inside the existing map block (before its closing })\n  if (newMapEntries.length > 0) {\n    const mapHeaderIdx = conf.indexOf(\"map $\");\n    if (mapHeaderIdx !== -1) {\n      let depth = 0;\n      let closeIdx = -1;\n      for (let i = mapHeaderIdx; i < conf.length; i++) {\n        if (conf[i] === \"{\") depth++;\n        else if (conf[i] === \"}\" && --depth === 0) { closeIdx = i; break; }\n      }\n      if (closeIdx !== -1) {\n        const injection = newMapEntries.map((e) => \"     \" + e).join(\"\\n\") + \"\\n\";\n        conf = conf.slice(0, closeIdx) + injection + conf.slice(closeIdx);\n      }\n    }\n  }\n\n  fs.writeFileSync(nginxConfPath, conf, \"utf8\");\n\n  // --- Upgrade ratelimiting-less rewrites to rate-limited ---\n  // When a new SLA introduces an endpoint that was previously \"ratelimiting-less\"\n  // (present in OAS but absent from all SLAs during the last full regen), the\n  // server-level if/rewrite block in nginx.conf is missing the ${api_client_name}_\n  // prefix and a stray `location ~` fallback exists. Both must be patched so\n  // requests are routed to the correct per-client conf.d location.\n  const rateLimitedEndpoints = [];\n  // Match rate-limited rewrites in the new full config:\n  //   rewrite /... \"/${api_client_name}_SANITIZED_${request_method}\" break;\n  const rateLimitedRe =\n    /rewrite\\s+\\S+\\s+\"\\/\\$\\{api_client_name\\}_([A-Za-z0-9-]+)_\\$\\{request_method\\}\"/g;\n  let rm;\n  while ((rm = rateLimitedRe.exec(fullConfig)) !== null) {\n    if (!rateLimitedEndpoints.includes(rm[1])) rateLimitedEndpoints.push(rm[1]);\n  }\n\n  if (rateLimitedEndpoints.length > 0) {\n    conf = fs.readFileSync(nginxConfPath, \"utf8\");\n    for (const sanitized of rateLimitedEndpoints) {\n      const esc = sanitized.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\n      // Replace ratelimiting-less rewrite (no ${api_client_name}_ prefix) with\n      // the rate-limited version.  The negative-lookahead skips lines that are\n      // already correct so the replacement is idempotent.\n      conf = conf.replace(\n        new RegExp(\n          `(rewrite\\\\s+\\\\S+\\\\s+)\"\\\\/${esc}_\\\\$\\\\{request_method\\\\}\"(\\\\s+break;)`,\n          \"g\"\n        ),\n        `$1\"/\\${api_client_name}_${sanitized}_\\${request_method}\"$2`\n      );\n\n      // Remove the stray `location ~ /SANITIZED_(...)` fallback block.\n      // These blocks proxy $uri_original (the un-stripped path) and cause 404s\n      // once a proper per-client conf.d location exists.\n      conf = conf.replace(\n        new RegExp(\n          `\\\\n?[ \\\\t]*location\\\\s+~\\\\s+\\\\/${esc}_\\\\([^)]+\\\\)\\\\s*\\\\{[^}]*\\\\}`,\n          \"gs\"\n        ),\n        \"\"\n      );\n    }\n    fs.writeFileSync(nginxConfPath, conf, \"utf8\");\n  }\n}\n\n/**\n * Updates an existing nginx.conf to remove a user's http-level directives\n * (limit_req_zone lines and map entry) when that user is deleted from conf.d.\n */\nfunction updateNginxConfRemoveUser(userKey, nginxConfPath) {\n  if (!fs.existsSync(nginxConfPath)) return;\n\n  let conf = fs.readFileSync(nginxConfPath, \"utf8\");\n\n  // Remove limit_req_zone lines whose zone name starts with <userKey>_\n  conf = conf.replace(\n    new RegExp(`[^\\\\n]*limit_req_zone[^\\\\n]+zone=${escapeRegex(userKey)}_[^\\\\n]*\\\\n?`, \"g\"),\n    \"\"\n  );\n\n  // Remove the map entry for this user: lines of the form \"~(...)\" \"userKey\";\n  conf = conf.replace(\n    new RegExp(`[^\\\\n]*\"~\\\\([^)]+\\\\)\"\\\\s+\"${escapeRegex(userKey)}\";[^\\\\n]*\\\\n?`, \"g\"),\n    \"\"\n  );\n\n  fs.writeFileSync(nginxConfPath, conf, \"utf8\");\n}\n\n/** Escapes special regex characters in a string. */\nfunction escapeRegex(str) {\n  return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nmodule.exports = { apply, configNginxConfd, addToConfd, removeFromConfd };\n"]}