Imported from udondan/iam-floyd (
AGENTS.md). Install upstream withnpx skills add udondan/iam-floyd. Copyright stays with the author.
AGENTS.md
This file provides guidance to AI agents when working with code in this repository.
Project Overview
IAM Floyd is an AWS IAM policy statement generator with a fluent interface. It generates TypeScript classes for all AWS services and their actions, resources, and condition keys from AWS documentation. The project supports both standalone usage (iam-floyd) and AWS CDK integration (cdk-iam-floyd).
Core Architecture
Generated Code Structure
lib/generated/model/- Service model per AWS service (JSON, committed). The single source of truth for all generated codelib/generated/policy-statements/- TypeScript class per AWS service, emitted from the model (not committed)lib/generated/index.ts- Re-exports all service classes (emitted, not committed)lib/generated/aws-managed-policies/- Generated AWS managed policies (committed)lib/shared/- Hand-written core:PolicyStatement,All,Operator,AccessLevellib/collection/- Predefined policy collection utilitieslib/generator/- Scrapes AWS docs withcheeriointo the model (model.ts), and emits TypeScript from the model withts-morph(emit/typescript.ts)
PolicyStatement Inheritance Chain
Built in 10 numbered layers (lib/shared/policy-statement/):
1-base → 2-conditions → 3-actions → 4-resources → 5-effect
→ 6-arn-defaults → 8-principals → 10-final (PolicyStatement)
Each *.CDK.ts file is the CDK variant of that layer (swapped in by bin/mkcdk.ts).
Dual Package Strategy
One codebase produces two npm packages:
iam-floyd- Standalone (uses built-in base class)cdk-iam-floyd- Extendsaws_iam.PolicyStatementfrom AWS CDK
bin/mkcdk.ts transforms between variants by swapping *.CDK.ts files and emitting the CDK variant of the service classes from the model.
Other Languages (jsii)
cdk-iam-floyd is also packaged for Python, Java, .NET and Go with jsii-pacmak. The jsii compiler is not used: lib/generator/emit/jsii.ts writes the .jsii assembly from the model, and bin/jsii.ts adds the jsii targets to package.json, writes the assembly and appends the jsii type info to lib/index.js. The same .jsii is what Construct Hub renders the API docs from. jsii-pacmak runs with --no-runtime-type-checking and bin/jsii-pack.ts as pack command, which embeds an npm tarball without docs and .d.ts files in the packages.
test/jsii/ builds floyd-consumer, a jsii library that depends on cdk-iam-floyd, and runs the same scenarios in TypeScript (the baseline, without jsii), Python, Java, .NET and Go against test/jsii/expected.json. It also runs the examples of the docs in each language (examples/<name>/<name>.{py,java,cs,go}, through the runners in test/jsii/examples/) and compares them to the .result files. Every example needs a file in every language.
Publishing: the npm package of cdk-iam-floyd includes the .jsii (make package-jsii publish LANGUAGES=typescript), Python goes to PyPI and .NET to NuGet (both trusted publishing), Java to Maven Central (bin/publish-maven, signed bundle via the Central Portal API). Go has no registry: bin/go-proxy writes the module zip, which is attached to the GitHub release, and a static Go module proxy for the newest 30 releases, served by GitHub Pages under udondan.github.io/iam-floyd/go.
Native Packages (transpiled core)
The standalone iam-floyd is being built natively for other languages, without jsii and without Node.js. The hand-written code in lib/shared/ (the core) and lib/collection/, and the names of the AWS managed policies (lib/generated/aws-managed-policies/iam-floyd.ts), are transpiled with ts-morph by lib/generator/transpile/ (index.ts collects the files of a module in import order, one backend per language, currently python.ts). The only imports from outside a module are the generated service classes, imported from their file (e.g. ../generated/policy-statements/ec2). The transpiler supports only a narrow subset of TypeScript: anything else fails with file and line, so rewrite the code in supported constructs rather than extending the transpiler for single cases. The service classes are emitted from the model by lib/generator/emit/python.ts, with the jsii naming rules (snake_case, if_, in_), into the package statement/, which imports a service on first access. bin/transpile.ts writes python/iam_floyd/_shared.py, _collection.py, _aws_managed_policies.py and statement/ (not committed). The package imports the collection and the managed policies on first access. python/iam_floyd/_js.py is the hand-written runtime for JavaScript semantics (number formatting, toISOString, sorting by UTF-16 code units, regular expressions).
test/transpile/ runs the scenarios against TypeScript (the baseline) and each language, and diffs the output: those of scenarios.json, which test the core, and those that services.ts builds from the model, which call every method of every service. Then it runs the examples of the docs (examples/<name>/<name>.py) with the native package, through python/examples.py, which runs them with iam_floyd in place of cdk_iam_floyd, and compares them to the .result files with test/jsii/examples/compare.py --standalone, which skips the *.cdk examples. Last, it compares the AWS managed policies with those of TypeScript.
The Python package is built with hatchling from python/pyproject.toml; bin/transpile.ts writes the version of package.json into iam_floyd/_version.py. It supports Python 3.9 and newer, and has no dependencies at runtime. CI builds it and tests the wheel with the oldest and the newest supported Python; for a release, the tested wheel and sdist go to PyPI as iam-floyd (trusted publishing, job publish-iam-floyd-python).
Development Commands
Build
make emit # emit lib/generated/policy-statements/ and lib/generated/index.ts from the model
make build # emit + tsc --build --force tsconfig.main.json tsconfig.types.json
make package # build + npm pack
make package-native # transpile and build the native packages of iam-floyd into dist/iam-floyd/ (Python: uv build)
make clean # remove node_modules, *.js, *.d.ts
make install # clean + npm i
Code Generation
make generate # scrape AWS docs into lib/generated/model/ and emit (25hr cache)
make generate-force # NOCACHE=1 - ignores time-based cache
make index-managed-policies # regenerate AWS managed policies index
make stats # update the counts in README.md and docs from the model
make changelog # print the changes of the managed policies and the model since the last tag
bin/model-list <services|actions|resources|conditions> prints those lists from the model, and bin/model-diff [ref] prints the differences to a git ref (default HEAD).
Testing
The project has no unit test framework. Tests are integration-style: TypeScript examples are compiled, run, and their output is diffed against stored .result files.
make test-typescript # compile examples/ + diff against *.result files (standalone)
make test-typescript-cdk # after `make cdk`: same for the CDK examples (examples/**/*.cdk.ts)
make cdk-test # CDK test: real deploy + destroy via AWS CDK
make cdk-all # cdk + install + build + cdk-test
make test-jsii # test the packages of `make package-jsii` against TypeScript; LANGUAGES=python limits the languages
make test-transpile # transpile, compare the scenarios of test/transpile/ with TypeScript and run the examples; PYTHON_WHEEL=<wheel> tests the built package
Run a single example test manually:
# 1. Compile a single example
npx tsc -p tsconfig.test-iam-floyd.json
# 2. Run and compare output
node examples/allow/allow.js > /tmp/out.txt
diff /tmp/out.txt examples/allow/allow.result
Regenerate expected results (after intentional changes):
make regenerate-code-example-results
Linting
make lint # emit + bin/lint: all linters, must pass in CI
make lint-fix # emit + bin/lint --fix: fixes what can be fixed
make lint-cdk # after `make cdk`: eslint on the CDK variant
bin/lint runs:
| Linter | Files |
|---|---|
eslint (eslint.config.mjs) |
TypeScript and JavaScript, with types |
| prettier | everything prettier knows, except .prettierignore |
| markdownlint-cli2 | Markdown |
| exact versions | the dependencies of all package.json files |
| ruff, ruff format | Python (ruff.toml, the examples are not formatted) |
| yamllint | YAML (.yamllint.yaml) |
| doc8 | reStructuredText in docs/source (doc8.ini) |
| zizmor, actionlint | GitHub workflows |
| shellcheck, shfmt | shell scripts, found by their shebang |
| gofmt | Go |
| dotnet format whitespace | C# in examples/ and test/jsii/ |
| checkstyle (Google style) | Java in examples/ and test/jsii/ (lint/pom.xml) |
The versions of the linters are pinned, so Renovate updates them: npm packages in package.json, Python tools in lint/requirements.txt (run with uv), Go tools in lint/go.mod (go tool) and checkstyle in lint/pom.xml. Locally, linters whose runtime (uv, Go, .NET, Maven) is missing are skipped; in CI they fail. In the CDK variant, eslint uses tsconfig.lint-cdk.json, which resolves cdk-iam-floyd to lib/, and prettier is off because mkcdk writes unformatted code.
The actions in the workflows are pinned to commit SHAs with the version as comment.
CDK Variant
make cdk # transforms codebase to CDK variant (modifies lib/shared, lib/generated, package.json)
make uncdk # reverts via git stash (lib/generated/aws-managed-policies, lib/shared, package.json) and re-emits
make package-jsii # after `make cdk`: build, write .jsii and run jsii-pacmak into dist/; LANGUAGES=python limits the languages
Fixing AWS Documentation Errors (lib/generator/fixes.ts)
The generator scrapes live AWS docs, which sometimes contain errors or inconsistencies. fixes.ts is the central place to patch these before code is generated. When the generator produces wrong output, add a fix here rather than editing generated files.
fixes object (keyed by URL slug)
Each top-level key is the URL slug of a service's IAM docs page (e.g. ec2, ssm, 'neptune-db'). Supported sub-keys:
| Sub-key | Effect |
|---|---|
ignore: true |
Skip generating this service entirely (used for EOL services) |
name: 'slug' |
Override the generated filename and class name (needed when the same service prefix spans multiple doc pages, e.g. pinpointemailservice → ses-pinpoint) |
service: 'prefix' |
Override the IAM service prefix used in the generated code |
resourceTypes.<name>.arn |
Replace the ARN template for a resource type with a corrected one |
conditions.<key>.key |
Rewrite the condition key string (used when docs have a concrete example key like RequestTag/tag-key instead of the parametric form RequestTag/${TagKey}) |
conditions.<key>.methodName |
Override the generated ifXxx() method name for a condition |
conditions.<key>.operator.type |
Override the inferred operator type (e.g. force date instead of string) |
conditions.<key>.operator.override |
Set typeOverride on the condition (used for custom operator generation) |
Exported fixer functions
conditionFixer(service, condition)— Normalises condition types (ArrayOfString→string,long→numeric, etc.) and applies any key/operator overrides fromfixes. Logs yellow[L1/L2 fix …]messages to stdout.conditionKeyFixer(service, key)— Rewrites a raw condition key string using anyconditions.<key>.keyoverride defined infixes.arnFixer(service, resource, arn)— Applies a cascade of ARN normalisation rules:- Uppercases the first letter of every
${placeholder}(L1) - Replaces trailing
*wildcards with${ResourceName}(L2) - Deduplicates repeated placeholder names by appending a counter (L2, Rekognition workaround)
- Applies the hard-coded ARN override from
fixesif present (L3) After fixing, validates the result against the canonical ARN regex and warns if it doesn't match (with a known-good exception list).
- Uppercases the first letter of every
serviceFixer(service)— Rewrites the IAM service prefix if aserviceoverride is defined infixes.
How to add a fix
- Find the service's URL slug from its IAM docs URL (e.g.
https://…/list_amazons3.html→ slug iss3). - Add an entry to the
fixesobject inlib/generator/fixes.ts. - Run
make generate-forceto regenerate with the fix applied.
File Modification Rules
CRITICAL: Never manually edit files in lib/generated/.
They are auto-generated from AWS documentation and will be overwritten on next make generate or make emit.
Allowed manual edits:
lib/shared/- Core shared classeslib/collection/- Predefined collectionslib/generator/- Generation logic and fixesbin/- CLI scriptstest/- Integration test scriptsexamples/- Example files and their.resultcounterparts
Code Style
Formatting (Prettier + ESLint)
- Indentation: 2 spaces (tabs only in Makefiles)
- Quotes: Single quotes in TypeScript/JS; double quotes in YAML/JSON
- Trailing newline: Required on all files
- No trailing whitespace
- Line endings: LF (
\n)
Run make lint to check; Prettier is enforced via eslint-plugin-prettier and for the other files by bin/lint. .editorconfig sets 4 spaces for Python and C#, and tabs for Go.
TypeScript
Strict settings enforced in tsconfig.json:
strict: true
noImplicitAny: true
strictNullChecks: true
strictPropertyInitialization: true
noUnusedLocals: true
noUnusedParameters: true
noImplicitReturns: true
target: ES2020, module: CommonJS
- No
any: Prefer explicit types or generics. - Unused vars: Prefix with
_to suppress (argsIgnorePattern: ^_). - Naming conventions: Enforced via
@typescript-eslint/naming-convention. UsecamelCasefor variables/functions,PascalCasefor classes/interfaces. - Template literals: Prefer
`${x}`over'a' + x(prefer-template: error). - Deprecated APIs:
@typescript-eslint/no-deprecatedrule is set toerror— do not use deprecated APIs. - No
require(): Use ESimport/export.
Imports
- Use named imports where possible:
import { Foo } from './foo' - Relative imports within
lib/; absolute fornode_modules lib/generated/is excluded from ESLint entirely
Fluent Interface Pattern
All service classes return this to allow chaining:
new Statement.S3()
.allow()
.toGetObject()
.on('arn:aws:s3:::my-bucket/*')
.ifAwsSourceVpc('vpc-123');
JSDoc
- All public methods in generated files have JSDoc with Access Level, conditions, resources, and an AWS docs URL
- In
lib/shared/, document non-obvious public methods and parameters
Generated Code Conventions
export class ServiceName extends PolicyStatement- Action methods:
toXxx()— e.g.,toGetObject(),toListBuckets() - Resource methods:
onXxx()— e.g.,onBucket(),onObject() - Condition methods:
ifXxx()— e.g.,ifAwsSourceIp(),ifS3Prefix() - All methods return
thisfor chaining
TypeScript Compilation
tsconfig.json- Dev/generation (includes all files, uses SWC viats-node)tsconfig.main.json- Production build of the.jsfiles, without comments and source maps (excludesbin/,lib/generator/,test/, CDK files)tsconfig.types.json- Production build of the.d.tsfiles, with JSDoctsconfig.test-iam-floyd.json- Compilesexamples/excluding.cdk.tstsconfig.test-cdk-iam-floyd.json- Compilesexamples/**/*.cdk.ts
SWC is used for faster transpilation: "ts-node": { "swc": true } in tsconfig.json.
Git Commit Conventions
Follow conventional commits:
feat: description- New featuresfix: description- Bug fixeschore(deps): description- Dependency updatesdocs: description- Documentation changesrefactor: description- Code refactoring- Simple prose for automated updates:
"Updates AWS managed policies"
Never commit directly to main. Use a feature branch. Commits may fail spell-checking; add missing words with dict-add <word>.
CI Workflows (.github/workflows/)
generate.yml- Weekly on Sunday: scrapes AWS docs, opens afeat:PR withautomergelabel if the model changedindex-managed-policies.yml- Weekly on Sunday: updates managed policies, opens afeat:PR withautomergelabelrelease-please.yml- On push to main: release-please maintains the release PR (version inpackage.jsonanddocs/source/conf.py,CHANGELOG.md). After each run,bin/changelog-add-iam-changesadds the changes of the managed policies and the model since the last release to the new changelog entry of the PR. Merging the PR creates the tag and a draft release, and startstest-and-publish.ymlwith the tagautomerge-schedule.yml- Weekly on Monday: merges the release PRtest-and-publish.yml- On PR:make install lint(joblint),make install test-typescript+make lint-cdk+ CDK deploy test +make package-jsii test-jsiiper language +make package-native test-transpileagainst the built Python package. Started byrelease-please.ymlwith a tag: builds the packages from the tag, publishes to npm, PyPI, NuGet, Maven Central and the Go module proxy on GitHub Pages, sets the notes of the release fromCHANGELOG.mdand publishes the releaseautomerge.yml- Auto-merges PRs labeledautomergeafter tests passtest-docs.yml- Builds Sphinx docs ondocs/**changes
