UNPKG

aws-iam-policy-types

Version:

Autogenerated Typescript types for AWS IAM Policy and enums for all policy actions

191 lines 8.41 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import fps from 'fs/promises'; import path from 'path'; import { JSDOM } from 'jsdom'; const ALL_SERVICES_URL = 'https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html'; const wait = (ms) => new Promise((res) => setTimeout(res, ms)); const uniqBy = (arr, keyFn = (x) => x) => { const seen = new Set(); return arr.reduce((agg, val, index) => { const key = keyFn(val, index); if (seen.has(key)) return agg; seen.add(key); agg.push(val); return agg; }, []); }; const fetchHtml = (url) => __awaiter(void 0, void 0, void 0, function* () { const html = yield fetch(url).then((r) => r.text()); const doc = new JSDOM(html).window.document; return doc; }); const formatMultilineComment = (lines) => { const formattedDesc = '/**\n' + lines.map((c) => ` * ${c}`.trimEnd()).join('\n') + '\n */'; return formattedDesc; }; const formatEnum = (input) => { const { name, comment, values } = input; const formattedValues = values .map(({ name, value, comment }) => { const formattedComment = comment ? formatMultilineComment(comment) + '\n' : ''; const formatted = `${formattedComment}${name} = ${value},`; return formatted; }) .join('\n'); const enumComment = comment ? formatMultilineComment(comment) + '\n' : ''; const enumStr = `// AUTOGENERATED FILE - DO NOT EDIT\n\n${enumComment}export enum ${name} {\n${tabText(formattedValues, 2)}\n}\n`; return enumStr; }; const tabText = (text, spaces = 2) => { return text .split('\n') .map((t) => ' '.repeat(spaces) + t) .join('\n'); }; const extractAllServices = () => __awaiter(void 0, void 0, void 0, function* () { const doc = yield fetchHtml(ALL_SERVICES_URL); // Select last list - Should be a list of all services const uls = [...doc.querySelectorAll('#main-content ul')]; const servicesListEl = uls[uls.length - 1]; const services = [...servicesListEl.querySelectorAll('li a')].reduce((agg, el) => { var _a; if (!el.textContent) return agg; const relativeUrl = el.href; const absoluteUrlObj = new URL(ALL_SERVICES_URL); const absolutePath = path.resolve(absoluteUrlObj.pathname, '..', relativeUrl); absoluteUrlObj.pathname = absolutePath; agg.push({ name: (_a = el.textContent) === null || _a === void 0 ? void 0 : _a.trim(), href: absoluteUrlObj.toString(), }); return agg; }, []); return services; }); const extractAwsPolicyActionsFromServicePage = (doc) => { var _a, _b, _c, _d; let rowsToIgnore = 0; const servicePrefix = (_b = (_a = doc.querySelector('code')) === null || _a === void 0 ? void 0 : _a.textContent) === null || _b === void 0 ? void 0 : _b.trim().toLowerCase(); if (!servicePrefix) { throw Error('Failed to extract service prefix from document'); } const actions = [ ...((_d = (_c = doc.querySelector('.table-container tbody')) === null || _c === void 0 ? void 0 : _c.querySelectorAll('tr')) !== null && _d !== void 0 ? _d : []), ].reduce((agg, el) => { var _a, _b, _c; // Some actions span multiple rows, so we ignore the additional rows with this if (rowsToIgnore > 0) { rowsToIgnore--; return agg; } const [nameEl, descEl] = el.querySelectorAll('td'); const linkEl = nameEl.querySelector('a'); if (nameEl.rowSpan) rowsToIgnore = nameEl.rowSpan - 1; const name = (_a = linkEl === null || linkEl === void 0 ? void 0 : linkEl.textContent) === null || _a === void 0 ? void 0 : _a.trim(); if (!name) return agg; const desc = ((_b = descEl.textContent) === null || _b === void 0 ? void 0 : _b.trim()) || ''; const link = (_c = linkEl === null || linkEl === void 0 ? void 0 : linkEl.href) !== null && _c !== void 0 ? _c : null; agg.push({ name, link, desc, }); return agg; }, []); return { actions, servicePrefix }; }; const formatAwsPolicyActionsForService = (input) => { const { actions, serviceName, servicePrefix, url } = input; const formattedActions = actions.map(({ desc, link, name }) => { // Split into chunks of up to 80 chars // See https://stackoverflow.com/a/8359929/9788634 const comment = [...(desc.match(/.{1,79}\s?/g) || [])].filter(Boolean); if (link) { comment.push('', `See ${link}`); } const value = `'${servicePrefix}:${name}'`; return { name, value, comment }; }); const formattedPrefix = servicePrefix .split('-') .map((s) => s[0].toUpperCase() + s.slice(1)) .join(''); const enumName = `Aws${formattedPrefix}Actions`; const enumComment = [ `All IAM policy actions for ${serviceName} (${servicePrefix.toUpperCase()})`, '', `Extracted by \`aws-iam-policy\` from`, url, '', new Date().toISOString(), ]; const actionsEnum = formatEnum({ name: enumName, comment: enumComment, values: formattedActions, }); return { name: enumName, content: actionsEnum }; }; export const extract = () => __awaiter(void 0, void 0, void 0, function* () { // Clear the dir where we save the extracted files const actionsDir = path.join(__dirname, 'actions'); yield fps.rm(actionsDir, { recursive: true, force: true }); yield fps.mkdir(actionsDir, { recursive: true }); // Extract the list of all services const services = yield extractAllServices(); const importPaths = []; for (const service of services) { // Extract specific page const doc = yield fetchHtml(service.href); try { const { actions, servicePrefix } = yield extractAwsPolicyActionsFromServicePage(doc); const { name, content } = formatAwsPolicyActionsForService({ url: service.href, serviceName: service.name, servicePrefix, actions, }); // Save it to a file const filename = path.join(actionsDir, `${servicePrefix}.ts`); yield fps.writeFile(filename, content, 'utf-8'); // Allow to import the file from the package importPaths.push({ name, path: `./actions/${servicePrefix}` }); } catch (e) { console.error('skipping', service, 'due to', e.message); } // Wait to avoid making rate limiting against AWS docs yield wait(250); } const uniqueImportPaths = uniqBy(importPaths, (p) => p.name); // Update the index file const indexFilename = path.join(__dirname, `index.ts`); const indexFileStr = '// AUTOGENERATED FILE - DO NOT EDIT\n\n' + uniqueImportPaths.map((p) => `export * from '${p.path}';`).join('\n') + "\n\nexport * from './types';\n"; yield fps.writeFile(indexFilename, indexFileStr, 'utf-8'); // Create a type that joins all the generated enums const typeFilename = path.join(__dirname, 'types', 'autogenerated.ts'); const typeFileStr = '// AUTOGENERATED FILE - DO NOT EDIT\n\n' + uniqueImportPaths.map((p) => `import type { ${p.name} } from '.${p.path}';`).join('\n') + '\n\nexport type AwsPolicyAction =\n' + uniqueImportPaths.map((p) => ` | ${p.name}`).join('\n') + ';\n'; yield fps.writeFile(typeFilename, typeFileStr, 'utf-8'); }); if (require.main === module) { extract(); } //# sourceMappingURL=extract.js.map