aws-iam-policy-types
Version:
Autogenerated Typescript types for AWS IAM Policy and enums for all policy actions
191 lines • 8.41 kB
JavaScript
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