Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/commands/module/scaffold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,68 @@ 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 @<owner>/<id>@<version> + 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"));
assert!(out.starts_with("module media\n"));
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);
Expand Down
2 changes: 1 addition & 1 deletion templates/module/go.mod.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
18 changes: 16 additions & 2 deletions templates/module/main.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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 <username>_<slug>_. 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)
Expand All @@ -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 @<owner>/<id>@<semver> 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()
Expand Down
Loading