diff --git a/src/commands/module/scaffold.rs b/src/commands/module/scaffold.rs index d55e188..49d1aea 100644 --- a/src/commands/module/scaffold.rs +++ b/src/commands/module/scaffold.rs @@ -138,6 +138,50 @@ mod tests { assert!(!out.contains("__MS_MODULE_ID__")); } + #[test] + fn render_substitutes_slug_into_config_slug() { + // Slug field is the SDK v0.2.0 catalog handle. The scaffolded + // module needs it set so the manifest carries it from day one; + // missing-slug behavior (dev-only mode) is for hand-written + // pre-publish modules, not the CLI scaffold. + let out = render(MAIN_GO, &ins("oauth", "OAuth")); + assert!( + out.contains(r#"Slug: "oauth""#), + "rendered main.go missing Config.Slug substitution" + ); + } + + #[test] + fn render_versions_default_is_dev_prerelease() { + // Pre-1.0 dev builds use `v0.1.0-dev` so `mirrorstack publish` can + // refuse `-dev` versions in prod by convention. Promotion + // happens at publish time. See docs/module-identity-and-storage-prefix.md. + let out = render(MAIN_GO, &ins("media", "Media")); + assert!( + out.contains(r#""v0.1.0-dev": {App: "0001"}"#), + "expected v0.1.0-dev as scaffold default version" + ); + assert!(!out.contains(r#""v0.1.0": {App: "0001"}"#)); + } + + #[test] + fn render_dependson_example_matches_v0_2_shape() { + // The doc-comment example must reflect the v0.2.0 contract: + // owner-prefixed id with version constraint, plus the Need + // callback declaring tables/events. The pre-v0.2.0 bare-id + // example (`ms.DependsOn("oauth-core")`) misled scaffolded + // modules into a shape the catalog won't accept anymore. + let out = render(MAIN_GO, &ins("media", "Media")); + assert!( + out.contains(r#"ms.DependsOn("@anna/oauth@^1.0.0", func(n *ms.Need)"#), + "DependsOn example must use @/@ + Need callback" + ); + assert!( + !out.contains(r#"ms.DependsOn("oauth-core")"#), + "stale bare-id DependsOn example still present" + ); + } + #[test] fn render_substitutes_in_go_mod() { let out = render(GO_MOD, &ins("media", "Media")); @@ -145,6 +189,17 @@ mod tests { assert!(!out.contains("__MS_SLUG__")); } + #[test] + fn render_go_mod_pins_sdk_v0_2() { + // Scaffolded modules must link against SDK v0.2.0+ to get Need / + // OptionalDependOn — the template's main.go uses both. + let out = render(GO_MOD, &ins("media", "Media")); + assert!( + out.contains("github.com/mirrorstack-ai/app-module-sdk v0.2.0"), + "go.mod must pin SDK v0.2.0 to match scaffolded API surface" + ); + } + #[test] fn sanitize_module_id_strips_hyphens_and_prepends_letter() { assert_eq!(sanitize_module_id(SAMPLE_UUID), SAMPLE_SANITIZED); diff --git a/templates/module/go.mod.tmpl b/templates/module/go.mod.tmpl index 5d9890e..3c0a471 100644 --- a/templates/module/go.mod.tmpl +++ b/templates/module/go.mod.tmpl @@ -4,5 +4,5 @@ go 1.26 require ( github.com/go-chi/chi/v5 v5.2.5 - github.com/mirrorstack-ai/app-module-sdk v0.1.1 + github.com/mirrorstack-ai/app-module-sdk v0.2.0 ) diff --git a/templates/module/main.go.tmpl b/templates/module/main.go.tmpl index 74d65a8..1296674 100644 --- a/templates/module/main.go.tmpl +++ b/templates/module/main.go.tmpl @@ -29,11 +29,18 @@ func main() { // changing this. Renaming this WOULD require migrating every schema // and table whose name is derived from it. ID: "__MS_MODULE_ID__", + // Slug is the catalog handle (e.g. "oauth"). Mutable via catalog UI; + // the storage prefix at install time is __. Renames + // force a new published version with auto-generated rename migrations. + Slug: "__MS_SLUG__", Name: "__MS_NAME__", Icon: "extension", SQL: sqlFS, Versions: map[string]system.MigrationVersions{ - "v0.1.0": {App: "0001"}, + // `-dev` prerelease tag distinguishes "iterating locally" from + // "real release". `mirrorstack publish` prompts to promote it + // before shipping; CI / prod refuses `-dev` versions. + "v0.1.0-dev": {App: "0001"}, }, }); err != nil { log.Fatalf("mirrorstack: init failed: %v", err) @@ -42,8 +49,15 @@ func main() { ms.Describe("Replace this description with your module's purpose.") // Required deps go here — these become install-time preconditions. + // Use @/@ for the spec; the optional callback + // declares which relations and events this module reads from the dep. + // The catalog validates names at install time and surfaces them for + // app-owner approval before issuing GRANT SELECT. // - // ms.DependsOn("oauth-core") + // ms.DependsOn("@anna/oauth@^1.0.0", func(n *ms.Need) { + // n.Table("oauth_users") + // n.Event("user.signed_in") + // }) for _, hook := range postInitHooks { hook()