Skip to content

feat(scaffold): create a new product module with one command - #101

Merged
kanushka merged 5 commits into
mainfrom
feat/module-scaffold-issue-96
Aug 22, 2026
Merged

feat(scaffold): create a new product module with one command#101
kanushka merged 5 commits into
mainfrom
feat/module-scaffold-issue-96

Conversation

@kanushka

@kanushka kanushka commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Closes #96. Stacked on #100#99#91; review those first.

make new-module NAMESPACE=mycloud

creates modules/mycloud, composes it in the workspace, and produces a module that builds and passes its own test with nothing edited. A developer's first hour goes on their product rather than on reconstructing the shape of a module from the reference module's source.

Two facts are read from the checkout rather than written into a template: the SDK and Cobra versions a module depends on, and the protocol versions it declares. A literal is correct until the next release and then produces a release-gate refusal the developer did not cause. They are read from the module graph with go mod edit -json rather than by scanning go.mod as text, because an exclude, a comment, or a versionless replace all mention a module path and only the graph knows which one is the requirement.

The generated module contains no authentication path. Acquiring access is where the interesting design lives, so the readme points at modules/reference as the complete worked example and says what this module deliberately leaves out.

Four namespaces are refused, and nothing is written when one is. The load-bearing refusal is a namespace a shell command owns: the shell resolves its own commands before consulting an installed module, so such a module would build, release, install, and then never run. The names come from app.CommandNames(), derived from the shell's own command list, so the check cannot fall behind a command being added.

How it is proven

One test generates a module, builds it, and runs the test the generation itself produced — which drives the module through the real module contract with sdk/testkit. A golden file would prove the template unchanged while the SDK moved out from under it; this proves the template works against the SDK as it is. It caught a wrong field name in the template during development, which is exactly the class of bug a golden file would have blessed.

Generation happens in a temporary repository, never in the checkout under test. Generating here would write into modules/ and edit go.work while the rest of the suite builds in parallel, so a package compiling at the wrong moment could see a workspace entry for a directory being deleted.

The test runs in the acceptance gate already, at its "Test the shell" stage — no script change needed.

Boundaries now cover every module

internal/boundaries listed its modules by hand ({".", "sdk", "modules/reference"}), so a scaffolded module was covered by none of its rules: not the license header, not the prohibition on replace directives, not workspace composition, and not the rule that a module may not import shell internals. Product modules are now discovered from modules/, so every boundary covers a module the moment it exists. This also makes the generated readme's claim about those rules true, which it otherwise would not have been.

Review fixes in the second commit

  • The workspace guard matched entries as substrings, so generating cloud when cloudops existed reported success while leaving the module out of the workspace — where it would then fail to resolve the SDK at all. Now matched as whole lines, with a test using exactly that pair.
  • The refusal for a namespace another module declares was never exercised: its test case reused reference, which short-circuits on the reserved check. There is now a fixture module declaring a namespace from a directory of another name, which is the case a directory check cannot see.
  • Whether the workspace can be joined is decided before anything is written, so no failure path leaves a partial module.
  • Two claims in the generated readme were untrue and are corrected.
  • Dropped a -repo flag and an exported RemoveFromWorkspace that nothing called.

Verification

go build ./..., go vet ./..., gofmt, golangci-lint run (0 issues), and the full go test ./... are clean apart from TestThePreviousProtocolGateResolvesThePublishedSDKItsGenerationNames, which is inherited from #91's head and described on #99.

Correction. Earlier revisions of this description said the full suite carries a failure inherited from #91 (TestThePreviousProtocolGateResolvesThePublishedSDKItsGenerationNames). That was wrong, and it is withdrawn. CI is green on #91 and on this branch. The failure reproduces only under Go 1.26, which is newer than the 1.25.x the workflows pin: under 1.26 the gate's file-proxy fixture stops resolving sdk/cobratree, although the fixture zip does contain it. The same commit passes with GOTOOLCHAIN=go1.25.6. It is a toolchain-compatibility problem in the gate's fixture, not a defect in #91 and not a defect in this change.

@kanushka
kanushka requested a review from hevayo as a code owner August 21, 2026 20:21
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@kanushka, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 47 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 880b098c-75d9-4acb-bd5e-a891965a85fe

📥 Commits

Reviewing files that changed from the base of the PR and between a345c7c and 888d29b.

📒 Files selected for processing (13)
  • CONTRIBUTING.md
  • Makefile
  • cmd/wso2-module-new/main.go
  • internal/app/app.go
  • internal/boundaries/boundaries_test.go
  • internal/scaffold/scaffold.go
  • internal/scaffold/scaffold_test.go
  • internal/scaffold/templates.go
  • internal/scaffold/templates/README.md.tmpl
  • internal/scaffold/templates/go.mod.tmpl
  • internal/scaffold/templates/main.go.tmpl
  • internal/scaffold/templates/main_test.go.tmpl
  • internal/scaffold/templates/module.json.tmpl

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds contributor tooling that scaffolds a new WSO2 CLI product module with a single command (make new-module NAMESPACE=<ns>). It creates modules/<namespace>, composes it into the workspace, and produces a module that builds and passes its own generated test with no editing. The SDK/Cobra versions and declared protocol versions are read from the checkout (via go mod edit -json and protocol.Supported()) rather than baked into a template, so a generated module never disagrees with what the repository builds. Four namespaces are refused before anything is written, and the boundaries tests now discover product modules from modules/ so every rule covers a scaffolded module the moment it exists.

Changes:

  • New internal/scaffold package (generator + embedded .tmpl files) plus cmd/wso2-module-new CLI and make new-module target, with namespace validation and atomic (all-or-nothing) generation.
  • internal/app.CommandNames() exposes shell command names so the "shadowed namespace" refusal cannot fall behind new commands.
  • internal/boundaries now discovers product modules dynamically instead of a hardcoded list, extending every boundary to generated modules.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
internal/scaffold/scaffold.go Core generation, namespace refusals, workspace composition, reference-version reading
internal/scaffold/templates.go Embeds template files and declares the generation file set
internal/scaffold/templates/*.tmpl Generated module's go.mod, module.json, main.go, main_test.go, README
internal/scaffold/scaffold_test.go Generates/builds/tests modules in a temp repo; asserts refusals and workspace joining
internal/app/app.go Adds CommandNames() derived from the shell's builtins
internal/boundaries/boundaries_test.go Discovers product modules so boundaries cover new modules
cmd/wso2-module-new/main.go CLI entry point invoking the scaffold generator
Makefile Adds new-module target with required NAMESPACE guard
CONTRIBUTING.md Documents the new-module flow

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +35 to +40
if outcome.Problem != nil {
t.Fatalf("status returned the problem %v", outcome.Problem)
}
if outcome.Result.Schema != StatusSchema {
t.Errorf("schema = %q, want %q", outcome.Result.Schema, StatusSchema)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and the template was the wrong place to get this wrong: every scaffolded module would have inherited it.

Fixed in 0e493b6. The generated test now checks outcome.Err first and outcome.Result == nil before reading the result, matching the guards in modules/reference that the readme points at. Verified by generating a module and running its test.

@kanushka
kanushka force-pushed the feat/module-scaffold-issue-96 branch from 9e96a2b to ed15a8c Compare August 22, 2026 05:29
@kanushka
kanushka force-pushed the feat/module-scaffold-issue-96 branch from ed15a8c to 7bf18d7 Compare August 22, 2026 05:34
@kanushka
kanushka force-pushed the feat/module-scaffold-issue-96 branch from 7bf18d7 to e27370e Compare August 22, 2026 05:35
Base automatically changed from feat/module-remove-issue-94 to main August 22, 2026 05:41
@kanushka
kanushka force-pushed the feat/module-scaffold-issue-96 branch from e27370e to 888d29b Compare August 22, 2026 05:41
@kanushka
kanushka merged commit 196aa6c into main Aug 22, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scaffold a new product module with one command

4 participants