diff --git a/.changeset/canonical-oauth-fixtures.md b/.changeset/canonical-oauth-fixtures.md
new file mode 100644
index 0000000..8382bee
--- /dev/null
+++ b/.changeset/canonical-oauth-fixtures.md
@@ -0,0 +1,5 @@
+---
+'@ankhorage/templates': minor
+---
+
+Add canonical Google, Apple, and combined OAuth manifest fixtures, and remove template-owned OAuth provider screen generation so Studio remains the single auth UI/runtime owner.
diff --git a/README.md b/README.md
index c4401ca..3a5b8ad 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
# TEMPLATES
-        
+        
Reusable Ankhorage app templates, presets, and manifest generators.
@@ -22,6 +22,8 @@ bunx @ankhorage/templates
- [Module relationships](././docs/diagrams/module-relationships.mmd)
- [Export graph](././docs/diagrams/export-graph.mmd)
- [ankhorage-templates sequence](././docs/diagrams/sequences/ankhorage-templates.mmd)
+- [createCategoryAppManifest sequence](././docs/diagrams/sequences/create-category-app-manifest.mmd)
+- [createOAuthFixtureManifest sequence](././docs/diagrams/sequences/create-oauth-fixture-manifest.mmd)
- [createStarterTemplate sequence](././docs/diagrams/sequences/create-starter-template.mmd)
- [listStarterTemplatesByCategory sequence](././docs/diagrams/sequences/list-starter-templates-by-category.mmd)
- [resolveStarterTemplate sequence](././docs/diagrams/sequences/resolve-starter-template.mmd)
diff --git a/bun.lock b/bun.lock
index 38eab25..02a3a7b 100644
--- a/bun.lock
+++ b/bun.lock
@@ -5,7 +5,7 @@
"": {
"name": "@ankhorage/templates",
"dependencies": {
- "@ankhorage/contracts": "^2.1.0",
+ "@ankhorage/contracts": "^3.0.0",
"@ankhorage/zora": "^2.8.0",
},
"devDependencies": {
@@ -25,7 +25,7 @@
"@ankhorage/color-theory": ["@ankhorage/color-theory@0.0.7", "", { "dependencies": { "culori": "^4.0.2" } }, "sha512-fcdJZMmhfbvc9Yhz5vjTolmWYAaCsUaUOq4/P6YDZLHhXer8Mv8y0LaOhWgaXcwq48nrxILGUMOxOl48tRhTmA=="],
- "@ankhorage/contracts": ["@ankhorage/contracts@2.1.0", "", { "dependencies": { "@ankhorage/color-theory": "^0.0.7" } }, "sha512-QGG0atpGDe9+BUWzWvV83Kj309TBOK76yBRbWOoQli8g0LY8QLb7Z4UquD+qVhtWJTp3Y1jHUTEttQ0WjdhY/Q=="],
+ "@ankhorage/contracts": ["@ankhorage/contracts@3.0.0", "", { "dependencies": { "@ankhorage/color-theory": "^0.0.7" } }, "sha512-IXBdPehcgJpDjl/0QXHunl7AsgkTAXoC89jBiQWE+tanp8SBdgVFxWMm9A1MtbU3Rr1UU6U81GjhASM2OhGVUw=="],
"@ankhorage/devtools": ["@ankhorage/devtools@1.0.6", "", { "dependencies": { "@eslint/js": "^10.0.1", "eslint": "^10.2.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-unused-imports": "^4.4.1", "knip": "^6.12.2", "prettier": "^3.8.1", "typescript-eslint": "^8.24.0" }, "bin": { "ankhorage-eslint": "dist/eslint-cli.js", "ankhorage-knip": "dist/knip-cli.js", "ankhorage-prettier": "dist/prettier-cli.js" } }, "sha512-QXvpJ3uvr3xcl/smjv0yMimtGRCt/dPs0lVdHYMAXg06u1HsZmoKhRyxdFX8OXcCv9RnfPNheUkjAbOrs0eeaQ=="],
diff --git a/docs/badges/npm.svg b/docs/badges/npm.svg
index 1562b37..1dbf9ac 100644
--- a/docs/badges/npm.svg
+++ b/docs/badges/npm.svg
@@ -1,7 +1,7 @@
-
Exports: createCategoryAppManifest
src/index.ts
Configured entrypoint
@@ -1373,14 +1395,17 @@ src/index.ts
Exports: APP_CATEGORIES, AppCategory,
CATEGORY_PRESETS, CategoryPreset,
CategoryStarterTemplateDefinition,
- createCategoryAppManifest, createStarterTemplate,
+ createCategoryAppManifest, createOAuthFixtureManifest,
+ createStarterTemplate, listOAuthFixtures,
listStarterTemplates, listStarterTemplatesByCategory,
- listStarterTemplateSummaries, resolveStarterTemplate,
- SplashScreenResizeMode, SplashScreenSpec,
- StarterTemplateFactory, StarterTemplateId,
- StarterTemplateOptions, StarterTemplateSelection,
- StarterTemplateSummary, TEMPLATE_KINDS,
- TemplateKind, TemplateSeed
+ listStarterTemplateSummaries, OAUTH_CALLBACK_ROUTE,
+ OAUTH_FIXTURE_IDS, OAuthFixtureDefinition,
+ OAuthFixtureId, resolveOAuthFixture,
+ resolveStarterTemplate, SplashScreenResizeMode,
+ SplashScreenSpec, StarterTemplateFactory,
+ StarterTemplateId, StarterTemplateOptions,
+ StarterTemplateSelection, StarterTemplateSummary,
+ TEMPLATE_KINDS, TemplateKind, TemplateSeed
SplashScreenSpec
+
+ src/fixtures/oauth.ts
+
+ createOAuthFixtureManifest
+ function • src/fixtures/oauth.ts:87:1
+
+ Export paths: src/index.ts
+
+
Related symbols:
+
+ - AppCategory
+ - OAuthFixtureId
+ - TemplateKind
+
+
+
+
Signature
+
+(args: { category: AppCategory; fixture: OAuthFixtureId; template?: TemplateKind; overrides?: AppManifestOverrides; }) => AppManifest
+
+
+
+ | Parameter |
+ Type |
+ Required |
+ Description |
+
+
+
+
+ args |
+
+ { category: AppCategory; fixture: OAuthFixtureId; template?:
+ TemplateKind; overrides?: AppManifestOverrides; }
+ |
+ yes |
+ |
+
+
+
+
Returns: AppManifest
+
+
+
+ listOAuthFixtures
+ function • src/fixtures/oauth.ts:79:1
+
+ Export paths: src/index.ts
+
+
Related symbols:
+
+ - OAuthFixtureDefinition
+
+
+
+
Signature
+
() => OAuthFixtureDefinition[]
+
+
Returns: OAuthFixtureDefinition[]
+
+
+
+ OAUTH_CALLBACK_ROUTE
+ value • src/fixtures/oauth.ts:8:14
+
+ Export paths: src/index.ts
+ Related symbols: None
+
+
+ OAUTH_FIXTURE_IDS
+ value • src/fixtures/oauth.ts:9:14
+
+ Export paths: src/index.ts
+ Related symbols: None
+
+
+ OAuthFixtureDefinition
+ type • src/fixtures/oauth.ts:16:1
+
+ Export paths: src/index.ts
+ Related symbols: None
+
+
+
+
+ | Member |
+ Kind |
+ Type |
+ Required |
+ Description |
+
+
+
+
+ description |
+ property |
+ string |
+ yes |
+ |
+
+
+ id |
+ property |
+
+ "google" | "apple" | "google-apple"
+ |
+ yes |
+ |
+
+
+ label |
+ property |
+ string |
+ yes |
+ |
+
+
+ oauth |
+ property |
+
+ import("/home/runner/work/templates/templates/node_modules/@ankhorage/contracts/dist/auth").AuthOAuthConfig
+ |
+ yes |
+ |
+
+
+
+
+
+ OAuthFixtureId
+ unknown • src/fixtures/oauth.ts:11:1
+
+ Export paths: src/index.ts
+ Related symbols: None
+
+
+ resolveOAuthFixture
+ function • src/fixtures/oauth.ts:83:1
+
+ Export paths: src/index.ts
+
+
Related symbols:
+
+ - OAuthFixtureDefinition
+
+
+
+
Signature
+
+(id: "google" | "apple" | "google-apple") => OAuthFixtureDefinition
+
+
+
+ | Parameter |
+ Type |
+ Required |
+ Description |
+
+
+
+
+ id |
+
+ "google" | "apple" | "google-apple"
+ |
+ yes |
+ |
+
+
+
+
Returns: OAuthFixtureDefinition
+
+
+
src/generators/create-category-app.ts
src/generators/create-category-app.ts
>
createCategoryAppManifest
- function • src/generators/create-category-app.ts:141:1
+ function • src/generators/create-category-app.ts:67:1
Export paths: src/index.ts
@@ -6006,17 +6243,20 @@ Architecture overview
module_src_projectSeed_ts module_src_commands_ts -->
module_src_templates_starter_index_ts module_src_commands_ts -->
module_src_templateSelector_ts
+ module_src_fixtures_oauth_ts["src/fixtures/oauth.ts"]
+ package__ankhorage_templates -.-> module_src_fixtures_oauth_ts
+ module_src_fixtures_oauth_ts --> module_src_generators_create_category_app_ts
+ module_src_fixtures_oauth_ts --> module_src_internal_merge_ts
+ module_src_fixtures_oauth_ts --> module_src_internal_overrides_ts
+ module_src_fixtures_oauth_ts --> module_src_templates_starter_index_ts
module_src_generators_create_category_app_ts["src/generators/create-category-app.ts"]
package__ankhorage_templates -.-> module_src_generators_create_category_app_ts
module_src_generators_create_category_app_ts --> module_src_internal_merge_ts
module_src_generators_create_category_app_ts --> module_src_internal_overrides_ts
module_src_generators_create_category_app_ts -->
module_src_presets_category_presets_ts module_src_generators_create_category_app_ts
- --> module_src_templates_shared_screen_ts
- module_src_generators_create_category_app_ts -->
- module_src_templates_shared_zora_node_helpers_ts
- module_src_generators_create_category_app_ts -->
- module_src_templates_starter_index_ts module_src_index_ts["src/index.ts"]
+ --> module_src_templates_starter_index_ts
+ module_src_index_ts["src/index.ts"]
module_src_internal_defaults_ts["src/internal/defaults.ts"]
package__ankhorage_templates -.-> module_src_internal_defaults_ts
module_src_internal_merge_ts["src/internal/merge.ts"]
@@ -7306,13 +7546,17 @@ Architecture overview
module_src_commands_ts --> module_src_projectSeed_ts
module_src_commands_ts --> module_src_templates_starter_index_ts
module_src_commands_ts --> module_src_templateSelector_ts
+ module_src_fixtures_oauth_ts["src/fixtures/oauth.ts"]
+ package__ankhorage_templates -.-> module_src_fixtures_oauth_ts
+ module_src_fixtures_oauth_ts --> module_src_generators_create_category_app_ts
+ module_src_fixtures_oauth_ts --> module_src_internal_merge_ts
+ module_src_fixtures_oauth_ts --> module_src_internal_overrides_ts
+ module_src_fixtures_oauth_ts --> module_src_templates_starter_index_ts
module_src_generators_create_category_app_ts["src/generators/create-category-app.ts"]
package__ankhorage_templates -.-> module_src_generators_create_category_app_ts
module_src_generators_create_category_app_ts --> module_src_internal_merge_ts
module_src_generators_create_category_app_ts --> module_src_internal_overrides_ts
module_src_generators_create_category_app_ts --> module_src_presets_category_presets_ts
- module_src_generators_create_category_app_ts --> module_src_templates_shared_screen_ts
- module_src_generators_create_category_app_ts --> module_src_templates_shared_zora_node_helpers_ts
module_src_generators_create_category_app_ts --> module_src_templates_starter_index_ts
module_src_index_ts["src/index.ts"]
module_src_internal_defaults_ts["src/internal/defaults.ts"]
@@ -8048,6 +8292,7 @@ Module relationships
module_src_cli_standalone_ts["src/cli/standalone.ts"]
module_src_commandContext_ts["src/commandContext.ts"]
module_src_commands_ts["src/commands.ts"]
+ module_src_fixtures_oauth_ts["src/fixtures/oauth.ts"]
module_src_generators_create_category_app_ts["src/generators/create-category-app.ts"]
module_src_index_ts["src/index.ts"]
module_src_internal_defaults_ts["src/internal/defaults.ts"]
@@ -8233,13 +8478,14 @@ Module relationships
module_src_packageMetadata_ts module_src_commands_ts -->
module_src_projectSeed_ts module_src_commands_ts -->
module_src_templates_starter_index_ts module_src_commands_ts -->
- module_src_templateSelector_ts module_src_generators_create_category_app_ts -->
- module_src_internal_merge_ts module_src_generators_create_category_app_ts -->
- module_src_internal_overrides_ts module_src_generators_create_category_app_ts -->
- module_src_presets_category_presets_ts module_src_generators_create_category_app_ts
- --> module_src_templates_shared_screen_ts
- module_src_generators_create_category_app_ts -->
- module_src_templates_shared_zora_node_helpers_ts
+ module_src_templateSelector_ts module_src_fixtures_oauth_ts -->
+ module_src_generators_create_category_app_ts module_src_fixtures_oauth_ts -->
+ module_src_internal_merge_ts module_src_fixtures_oauth_ts -->
+ module_src_internal_overrides_ts module_src_fixtures_oauth_ts -->
+ module_src_templates_starter_index_ts module_src_generators_create_category_app_ts
+ --> module_src_internal_merge_ts module_src_generators_create_category_app_ts
+ --> module_src_internal_overrides_ts module_src_generators_create_category_app_ts
+ --> module_src_presets_category_presets_ts
module_src_generators_create_category_app_ts -->
module_src_templates_starter_index_ts module_src_internal_merge_ts -->
module_src_internal_overrides_ts module_src_packageMetadata_ts -->
@@ -9021,6 +9267,7 @@ Module relationships
module_src_cli_standalone_ts["src/cli/standalone.ts"]
module_src_commandContext_ts["src/commandContext.ts"]
module_src_commands_ts["src/commands.ts"]
+ module_src_fixtures_oauth_ts["src/fixtures/oauth.ts"]
module_src_generators_create_category_app_ts["src/generators/create-category-app.ts"]
module_src_index_ts["src/index.ts"]
module_src_internal_defaults_ts["src/internal/defaults.ts"]
@@ -9206,11 +9453,13 @@ Module relationships
module_src_commands_ts --> module_src_projectSeed_ts
module_src_commands_ts --> module_src_templates_starter_index_ts
module_src_commands_ts --> module_src_templateSelector_ts
+ module_src_fixtures_oauth_ts --> module_src_generators_create_category_app_ts
+ module_src_fixtures_oauth_ts --> module_src_internal_merge_ts
+ module_src_fixtures_oauth_ts --> module_src_internal_overrides_ts
+ module_src_fixtures_oauth_ts --> module_src_templates_starter_index_ts
module_src_generators_create_category_app_ts --> module_src_internal_merge_ts
module_src_generators_create_category_app_ts --> module_src_internal_overrides_ts
module_src_generators_create_category_app_ts --> module_src_presets_category_presets_ts
- module_src_generators_create_category_app_ts --> module_src_templates_shared_screen_ts
- module_src_generators_create_category_app_ts --> module_src_templates_shared_zora_node_helpers_ts
module_src_generators_create_category_app_ts --> module_src_templates_starter_index_ts
module_src_internal_merge_ts --> module_src_internal_overrides_ts
module_src_packageMetadata_ts --> module_package_json
@@ -9608,6 +9857,7 @@ Export graph
module_src_cli_standalone_ts["src/cli/standalone.ts"]
module_src_commandContext_ts["src/commandContext.ts"]
module_src_commands_ts["src/commands.ts"]
+ module_src_fixtures_oauth_ts["src/fixtures/oauth.ts"]
module_src_generators_create_category_app_ts["src/generators/create-category-app.ts"]
module_src_index_ts["src/index.ts"]
module_src_internal_defaults_ts["src/internal/defaults.ts"]
@@ -9792,10 +10042,18 @@ Export graph
export_createCategoryAppManifest["createCategoryAppManifest"]
module_src_generators_create_category_app_ts --> export_createCategoryAppManifest
export_createCategoryAppManifest -.-> export_SplashScreenResizeMode
+ export_createOAuthFixtureManifest["createOAuthFixtureManifest"]
+ module_src_fixtures_oauth_ts --> export_createOAuthFixtureManifest
+ export_createOAuthFixtureManifest -.-> export_AppCategory
+ export_createOAuthFixtureManifest -.-> export_OAuthFixtureId
+ export_createOAuthFixtureManifest -.-> export_TemplateKind
export_createStarterTemplate["createStarterTemplate"]
module_src_templates_starter_starter_template_ts --> export_createStarterTemplate
export_createStarterTemplate -.-> export_StarterTemplateOptions
export_createStarterTemplate -.-> export_TemplateSeed
+ export_listOAuthFixtures["listOAuthFixtures"] module_src_fixtures_oauth_ts
+ --> export_listOAuthFixtures export_listOAuthFixtures -.->
+ export_OAuthFixtureDefinition
export_listStarterTemplates["listStarterTemplates"]
module_src_templates_starter_starter_registry_ts --> export_listStarterTemplates
export_listStarterTemplates -.-> export_CategoryStarterTemplateDefinition
@@ -9807,6 +10065,17 @@ Export graph
module_src_templates_starter_starter_registry_ts -->
export_listStarterTemplateSummaries export_listStarterTemplateSummaries -.->
export_StarterTemplateSummary
+ export_OAUTH_CALLBACK_ROUTE["OAUTH_CALLBACK_ROUTE"]
+ module_src_fixtures_oauth_ts --> export_OAUTH_CALLBACK_ROUTE
+ export_OAUTH_FIXTURE_IDS["OAUTH_FIXTURE_IDS"] module_src_fixtures_oauth_ts
+ --> export_OAUTH_FIXTURE_IDS
+ export_OAuthFixtureDefinition["OAuthFixtureDefinition"]
+ module_src_fixtures_oauth_ts --> export_OAuthFixtureDefinition
+ export_OAuthFixtureId["OAuthFixtureId"] module_src_fixtures_oauth_ts
+ --> export_OAuthFixtureId
+ export_resolveOAuthFixture["resolveOAuthFixture"]
+ module_src_fixtures_oauth_ts --> export_resolveOAuthFixture
+ export_resolveOAuthFixture -.-> export_OAuthFixtureDefinition
export_resolveStarterTemplate["resolveStarterTemplate"]
module_src_templates_starter_starter_registry_ts -->
export_resolveStarterTemplate export_resolveStarterTemplate -.->
@@ -9845,6 +10114,7 @@ Export graph
module_src_cli_standalone_ts["src/cli/standalone.ts"]
module_src_commandContext_ts["src/commandContext.ts"]
module_src_commands_ts["src/commands.ts"]
+ module_src_fixtures_oauth_ts["src/fixtures/oauth.ts"]
module_src_generators_create_category_app_ts["src/generators/create-category-app.ts"]
module_src_index_ts["src/index.ts"]
module_src_internal_defaults_ts["src/internal/defaults.ts"]
@@ -10028,10 +10298,18 @@ Export graph
export_createCategoryAppManifest["createCategoryAppManifest"]
module_src_generators_create_category_app_ts --> export_createCategoryAppManifest
export_createCategoryAppManifest -.-> export_SplashScreenResizeMode
+ export_createOAuthFixtureManifest["createOAuthFixtureManifest"]
+ module_src_fixtures_oauth_ts --> export_createOAuthFixtureManifest
+ export_createOAuthFixtureManifest -.-> export_AppCategory
+ export_createOAuthFixtureManifest -.-> export_OAuthFixtureId
+ export_createOAuthFixtureManifest -.-> export_TemplateKind
export_createStarterTemplate["createStarterTemplate"]
module_src_templates_starter_starter_template_ts --> export_createStarterTemplate
export_createStarterTemplate -.-> export_StarterTemplateOptions
export_createStarterTemplate -.-> export_TemplateSeed
+ export_listOAuthFixtures["listOAuthFixtures"]
+ module_src_fixtures_oauth_ts --> export_listOAuthFixtures
+ export_listOAuthFixtures -.-> export_OAuthFixtureDefinition
export_listStarterTemplates["listStarterTemplates"]
module_src_templates_starter_starter_registry_ts --> export_listStarterTemplates
export_listStarterTemplates -.-> export_CategoryStarterTemplateDefinition
@@ -10041,6 +10319,17 @@ Export graph
export_listStarterTemplateSummaries["listStarterTemplateSummaries"]
module_src_templates_starter_starter_registry_ts --> export_listStarterTemplateSummaries
export_listStarterTemplateSummaries -.-> export_StarterTemplateSummary
+ export_OAUTH_CALLBACK_ROUTE["OAUTH_CALLBACK_ROUTE"]
+ module_src_fixtures_oauth_ts --> export_OAUTH_CALLBACK_ROUTE
+ export_OAUTH_FIXTURE_IDS["OAUTH_FIXTURE_IDS"]
+ module_src_fixtures_oauth_ts --> export_OAUTH_FIXTURE_IDS
+ export_OAuthFixtureDefinition["OAuthFixtureDefinition"]
+ module_src_fixtures_oauth_ts --> export_OAuthFixtureDefinition
+ export_OAuthFixtureId["OAuthFixtureId"]
+ module_src_fixtures_oauth_ts --> export_OAuthFixtureId
+ export_resolveOAuthFixture["resolveOAuthFixture"]
+ module_src_fixtures_oauth_ts --> export_resolveOAuthFixture
+ export_resolveOAuthFixture -.-> export_OAuthFixtureDefinition
export_resolveStarterTemplate["resolveStarterTemplate"]
module_src_templates_starter_starter_registry_ts --> export_resolveStarterTemplate
export_resolveStarterTemplate -.-> export_CategoryStarterTemplateDefinition
@@ -10140,6 +10429,132 @@ ankhorage-templates sequence
participant_renderUnknownCommand-->>participant_runCli: return
participant_runCli->>participant_runCommand: runCommand()
participant_runCommand-->>participant_runCli: return
+
+
+
+
+ createCategoryAppManifest sequence
+ diagrams/sequences/create-category-app-manifest.mmd
+
+ sequenceDiagram participant participant___type as __type participant
+ participant_createCategoryAppManifest as createCategoryAppManifest participant
+ participant_createManifestFromTemplate as createManifestFromTemplate participant
+ participant_isPlainObject as isPlainObject participant participant_mergeAppManifest
+ as mergeAppManifest participant participant_mergeValue as mergeValue
+ participant_createCategoryAppManifest->>participant_createManifestFromTemplate:
+ createManifestFromTemplate()
+ participant_createManifestFromTemplate->>participant___type:
+ TEMPLATE_FACTORIES[template]()
+ participant___type-->>participant_createManifestFromTemplate: return
+ participant_createManifestFromTemplate-->>participant_createCategoryAppManifest:
+ return participant_createCategoryAppManifest->>participant_mergeAppManifest:
+ mergeAppManifest() participant_mergeAppManifest->>participant_mergeValue:
+ mergeValue() participant_mergeValue->>participant_isPlainObject:
+ isPlainObject() participant_isPlainObject-->>participant_mergeValue: return
+ participant_mergeValue->>participant_mergeValue: mergeValue()
+ participant_mergeValue-->>participant_mergeValue: return
+ participant_mergeValue-->>participant_mergeAppManifest: return
+ participant_mergeAppManifest-->>participant_createCategoryAppManifest: return
+
+
+ View Mermaid source
+
+sequenceDiagram
+ participant participant___type as __type
+ participant participant_createCategoryAppManifest as createCategoryAppManifest
+ participant participant_createManifestFromTemplate as createManifestFromTemplate
+ participant participant_isPlainObject as isPlainObject
+ participant participant_mergeAppManifest as mergeAppManifest
+ participant participant_mergeValue as mergeValue
+ participant_createCategoryAppManifest->>participant_createManifestFromTemplate: createManifestFromTemplate()
+ participant_createManifestFromTemplate->>participant___type: TEMPLATE_FACTORIES[template]()
+ participant___type-->>participant_createManifestFromTemplate: return
+ participant_createManifestFromTemplate-->>participant_createCategoryAppManifest: return
+ participant_createCategoryAppManifest->>participant_mergeAppManifest: mergeAppManifest()
+ participant_mergeAppManifest->>participant_mergeValue: mergeValue()
+ participant_mergeValue->>participant_isPlainObject: isPlainObject()
+ participant_isPlainObject-->>participant_mergeValue: return
+ participant_mergeValue->>participant_mergeValue: mergeValue()
+ participant_mergeValue-->>participant_mergeValue: return
+ participant_mergeValue-->>participant_mergeAppManifest: return
+ participant_mergeAppManifest-->>participant_createCategoryAppManifest: return
+
+
+
+
+ createOAuthFixtureManifest sequence
+ diagrams/sequences/create-oauth-fixture-manifest.mmd
+
+ sequenceDiagram participant participant___type as __type participant
+ participant_createCategoryAppManifest as createCategoryAppManifest participant
+ participant_createManifestFromTemplate as createManifestFromTemplate participant
+ participant_createOAuthFixtureManifest as createOAuthFixtureManifest participant
+ participant_isPlainObject as isPlainObject participant participant_mergeAppManifest
+ as mergeAppManifest participant participant_mergeValue as mergeValue participant
+ participant_resolveOAuthFixture as resolveOAuthFixture
+ participant_createOAuthFixtureManifest->>participant_createCategoryAppManifest:
+ createCategoryAppManifest()
+ participant_createCategoryAppManifest->>participant_createManifestFromTemplate:
+ createManifestFromTemplate()
+ participant_createManifestFromTemplate->>participant___type:
+ TEMPLATE_FACTORIES[template]()
+ participant___type-->>participant_createManifestFromTemplate: return
+ participant_createManifestFromTemplate-->>participant_createCategoryAppManifest:
+ return participant_createCategoryAppManifest->>participant_mergeAppManifest:
+ mergeAppManifest() participant_mergeAppManifest->>participant_mergeValue:
+ mergeValue() participant_mergeValue->>participant_isPlainObject:
+ isPlainObject() participant_isPlainObject-->>participant_mergeValue: return
+ participant_mergeValue->>participant_mergeValue: mergeValue()
+ participant_mergeValue-->>participant_mergeValue: return
+ participant_mergeValue-->>participant_mergeAppManifest: return
+ participant_mergeAppManifest-->>participant_createCategoryAppManifest: return
+ participant_createCategoryAppManifest-->>participant_createOAuthFixtureManifest:
+ return
+ participant_createOAuthFixtureManifest->>participant_resolveOAuthFixture:
+ resolveOAuthFixture()
+ participant_resolveOAuthFixture-->>participant_createOAuthFixtureManifest:
+ return participant_createOAuthFixtureManifest->>participant_mergeAppManifest:
+ mergeAppManifest()
+ participant_mergeAppManifest-->>participant_createOAuthFixtureManifest: return
+
+
+ View Mermaid source
+
+sequenceDiagram
+ participant participant___type as __type
+ participant participant_createCategoryAppManifest as createCategoryAppManifest
+ participant participant_createManifestFromTemplate as createManifestFromTemplate
+ participant participant_createOAuthFixtureManifest as createOAuthFixtureManifest
+ participant participant_isPlainObject as isPlainObject
+ participant participant_mergeAppManifest as mergeAppManifest
+ participant participant_mergeValue as mergeValue
+ participant participant_resolveOAuthFixture as resolveOAuthFixture
+ participant_createOAuthFixtureManifest->>participant_createCategoryAppManifest: createCategoryAppManifest()
+ participant_createCategoryAppManifest->>participant_createManifestFromTemplate: createManifestFromTemplate()
+ participant_createManifestFromTemplate->>participant___type: TEMPLATE_FACTORIES[template]()
+ participant___type-->>participant_createManifestFromTemplate: return
+ participant_createManifestFromTemplate-->>participant_createCategoryAppManifest: return
+ participant_createCategoryAppManifest->>participant_mergeAppManifest: mergeAppManifest()
+ participant_mergeAppManifest->>participant_mergeValue: mergeValue()
+ participant_mergeValue->>participant_isPlainObject: isPlainObject()
+ participant_isPlainObject-->>participant_mergeValue: return
+ participant_mergeValue->>participant_mergeValue: mergeValue()
+ participant_mergeValue-->>participant_mergeValue: return
+ participant_mergeValue-->>participant_mergeAppManifest: return
+ participant_mergeAppManifest-->>participant_createCategoryAppManifest: return
+ participant_createCategoryAppManifest-->>participant_createOAuthFixtureManifest: return
+ participant_createOAuthFixtureManifest->>participant_resolveOAuthFixture: resolveOAuthFixture()
+ participant_resolveOAuthFixture-->>participant_createOAuthFixtureManifest: return
+ participant_createOAuthFixtureManifest->>participant_mergeAppManifest: mergeAppManifest()
+ participant_mergeAppManifest-->>participant_createOAuthFixtureManifest: return
@@ -10430,6 +10845,36 @@ isHelpToken
+
+
+ src/fixtures/oauth.ts
+
+ createOAuthConfig
+ src/fixtures/oauth.ts:23:1
+ No description available.
+
+
+ listOAuthFixtures
+ src/fixtures/oauth.ts:79:1
+ No description available.
+
+
+ resolveOAuthFixture
+ src/fixtures/oauth.ts:83:1
+ No description available.
+
+
+ createOAuthFixtureManifest
+ src/fixtures/oauth.ts:87:1
+ No description available.
+
+
+
src/generators/create-category-app.ts
data-search="resolveThemeModeValue src/generators/create-category-app.ts "
>
resolveThemeModeValue
- src/generators/create-category-app.ts:24:1
+ src/generators/create-category-app.ts:8:1
No description available.
resolveThemeModeValue
data-search="resolveSeedName src/generators/create-category-app.ts "
>
resolveSeedName
- src/generators/create-category-app.ts:33:1
+ src/generators/create-category-app.ts:17:1
No description available.
resolveSeedName
data-search="resolveSeedSlug src/generators/create-category-app.ts "
>
resolveSeedSlug
- src/generators/create-category-app.ts:37:1
+ src/generators/create-category-app.ts:21:1
No description available.
resolveSeedSlug
data-search="resolveSeedThemeId src/generators/create-category-app.ts "
>
resolveSeedThemeId
- src/generators/create-category-app.ts:41:1
+ src/generators/create-category-app.ts:25:1
No description available.
resolveSeedThemeId
data-search="createManifestFromTemplate src/generators/create-category-app.ts "
>
createManifestFromTemplate
- src/generators/create-category-app.ts:75:1
- No description available.
-
-
- addProviderEntryScreen
- src/generators/create-category-app.ts:83:1
- No description available.
-
-
- createProviderEntryScreen
- src/generators/create-category-app.ts:111:1
+ src/generators/create-category-app.ts:59:1
No description available.
createProviderEntryScreen
data-search="createCategoryAppManifest src/generators/create-category-app.ts "
>
createCategoryAppManifest
- src/generators/create-category-app.ts:141:1
+ src/generators/create-category-app.ts:67:1
No description available.
diff --git a/docs/oauth-fixtures.md b/docs/oauth-fixtures.md
new file mode 100644
index 0000000..bf788b1
--- /dev/null
+++ b/docs/oauth-fixtures.md
@@ -0,0 +1,69 @@
+# OAuth fixture setup
+
+The first-party OAuth fixtures configure the logical application callback route
+`auth/callback`. They never contain provider credentials or a complete deployment URL.
+Studio resolves that route into the platform-specific redirect URI when it generates the app.
+
+## Keep the two redirect layers separate
+
+OAuth through Supabase uses two different redirect layers:
+
+1. The provider redirects to Supabase Auth.
+2. Supabase Auth redirects back to the generated application.
+
+Do not register the application callback directly as the Google or Apple provider callback.
+
+## Google provider console
+
+Configure a Web application OAuth client in Google Auth Platform.
+
+- Authorized JavaScript origin: the application origin, such as `https://app.example.com`.
+- Authorized redirect URI: the callback shown on the Google provider page in the Supabase
+ Dashboard.
+- Hosted-project example: `https://.supabase.co/auth/v1/callback`.
+- Local Supabase example: `http://127.0.0.1:54321/auth/v1/callback`.
+
+When the Supabase project uses a custom Auth domain, use that domain's callback instead of the
+`supabase.co` example.
+
+Official reference:
+[Supabase Login with Google](https://supabase.com/docs/guides/auth/social-login/auth-google)
+
+## Apple developer console
+
+Configure Sign in with Apple for the App ID and Services ID used by the application.
+
+For the Services ID Website URLs:
+
+- Domain: the domain hosting Supabase Auth, commonly `.supabase.co`.
+- Return URL: `https://.supabase.co/auth/v1/callback`.
+
+Use the actual callback shown by the Supabase Apple provider configuration when a custom Auth
+hostname is configured.
+
+Official reference:
+[Supabase Login with Apple](https://supabase.com/docs/guides/auth/social-login/auth-apple)
+
+## Supabase redirect allow list
+
+The application-facing redirect URI must be added to the Supabase Auth Redirect URLs list.
+The fixture route resolves to values such as:
+
+- Web: `https://app.example.com/auth/callback`.
+- Local web: `http://localhost:/auth/callback`.
+- Native development or production build: `://auth/callback`.
+
+Use the exact generated scheme from the app configuration. Prefer exact production redirect URLs;
+reserve wildcard patterns for controlled local or preview environments.
+
+Official references:
+
+- [Supabase Redirect URLs](https://supabase.com/docs/guides/auth/redirect-urls)
+- [Supabase Native Mobile Deep Linking](https://supabase.com/docs/guides/auth/native-mobile-deep-linking)
+
+## What the fixtures prove
+
+The fixtures and their tests prove deterministic manifest generation, supported provider IDs,
+logical credential references, canonical callback routing, and absence of secret-shaped fields.
+They do not prove that an external Google or Apple tenant has been configured correctly, and they
+do not perform live provider authentication.
diff --git a/docs/paradox.json b/docs/paradox.json
index 9a356b6..0ec60e6 100644
--- a/docs/paradox.json
+++ b/docs/paradox.json
@@ -12,7 +12,7 @@
{
"id": "npm",
"label": "npm",
- "value": "v2.0.0",
+ "value": "v2.1.0",
"color": "cb3837"
},
{
@@ -144,6 +144,25 @@
"TemplatesCommandInvocation"
]
},
+ {
+ "path": "src/fixtures/oauth.ts",
+ "isEntrypoint": false,
+ "dependencies": [
+ "src/generators/create-category-app.ts",
+ "src/internal/merge.ts",
+ "src/internal/overrides.ts",
+ "src/templates/starter/index.ts"
+ ],
+ "exports": [
+ "createOAuthFixtureManifest",
+ "listOAuthFixtures",
+ "OAUTH_CALLBACK_ROUTE",
+ "OAUTH_FIXTURE_IDS",
+ "OAuthFixtureDefinition",
+ "OAuthFixtureId",
+ "resolveOAuthFixture"
+ ]
+ },
{
"path": "src/generators/create-category-app.ts",
"isEntrypoint": false,
@@ -151,8 +170,6 @@
"src/internal/merge.ts",
"src/internal/overrides.ts",
"src/presets/category-presets.ts",
- "src/templates/shared/screen.ts",
- "src/templates/shared/zora-node-helpers.ts",
"src/templates/starter/index.ts"
],
"exports": ["createCategoryAppManifest"]
@@ -168,10 +185,17 @@
"CategoryPreset",
"CategoryStarterTemplateDefinition",
"createCategoryAppManifest",
+ "createOAuthFixtureManifest",
"createStarterTemplate",
+ "listOAuthFixtures",
"listStarterTemplates",
"listStarterTemplatesByCategory",
"listStarterTemplateSummaries",
+ "OAUTH_CALLBACK_ROUTE",
+ "OAUTH_FIXTURE_IDS",
+ "OAuthFixtureDefinition",
+ "OAuthFixtureId",
+ "resolveOAuthFixture",
"resolveStarterTemplate",
"SplashScreenResizeMode",
"SplashScreenSpec",
@@ -1957,7 +1981,7 @@
"modulePath": "src/generators/create-category-app.ts",
"sourceLocation": {
"filePath": "src/generators/create-category-app.ts",
- "line": 141,
+ "line": 67,
"column": 1
},
"exportPaths": ["src/index.ts"],
@@ -1992,6 +2016,38 @@
"members": [],
"structuredRows": []
},
+ {
+ "name": "createOAuthFixtureManifest",
+ "description": null,
+ "isReadme": false,
+ "examples": [],
+ "kind": "function",
+ "modulePath": "src/fixtures/oauth.ts",
+ "sourceLocation": {
+ "filePath": "src/fixtures/oauth.ts",
+ "line": 87,
+ "column": 1
+ },
+ "exportPaths": ["src/index.ts"],
+ "relatedSymbols": ["AppCategory", "OAuthFixtureId", "TemplateKind"],
+ "signatures": [
+ {
+ "label": "(args: { category: AppCategory; fixture: OAuthFixtureId; template?: TemplateKind; overrides?: AppManifestOverrides; }) => AppManifest",
+ "parameters": [
+ {
+ "name": "args",
+ "type": "{ category: AppCategory; fixture: OAuthFixtureId; template?: TemplateKind; overrides?: AppManifestOverrides; }",
+ "required": true,
+ "description": null
+ }
+ ],
+ "returnType": "AppManifest",
+ "returnDescription": null
+ }
+ ],
+ "members": [],
+ "structuredRows": []
+ },
{
"name": "createStarterTemplate",
"description": null,
@@ -2030,6 +2086,31 @@
"members": [],
"structuredRows": []
},
+ {
+ "name": "listOAuthFixtures",
+ "description": null,
+ "isReadme": false,
+ "examples": [],
+ "kind": "function",
+ "modulePath": "src/fixtures/oauth.ts",
+ "sourceLocation": {
+ "filePath": "src/fixtures/oauth.ts",
+ "line": 79,
+ "column": 1
+ },
+ "exportPaths": ["src/index.ts"],
+ "relatedSymbols": ["OAuthFixtureDefinition"],
+ "signatures": [
+ {
+ "label": "() => OAuthFixtureDefinition[]",
+ "parameters": [],
+ "returnType": "OAuthFixtureDefinition[]",
+ "returnDescription": null
+ }
+ ],
+ "members": [],
+ "structuredRows": []
+ },
{
"name": "listStarterTemplates",
"description": null,
@@ -2119,6 +2200,139 @@
"members": [],
"structuredRows": []
},
+ {
+ "name": "OAUTH_CALLBACK_ROUTE",
+ "description": null,
+ "isReadme": false,
+ "examples": [],
+ "kind": "value",
+ "modulePath": "src/fixtures/oauth.ts",
+ "sourceLocation": {
+ "filePath": "src/fixtures/oauth.ts",
+ "line": 8,
+ "column": 14
+ },
+ "exportPaths": ["src/index.ts"],
+ "relatedSymbols": [],
+ "signatures": [],
+ "members": [],
+ "structuredRows": []
+ },
+ {
+ "name": "OAUTH_FIXTURE_IDS",
+ "description": null,
+ "isReadme": false,
+ "examples": [],
+ "kind": "value",
+ "modulePath": "src/fixtures/oauth.ts",
+ "sourceLocation": {
+ "filePath": "src/fixtures/oauth.ts",
+ "line": 9,
+ "column": 14
+ },
+ "exportPaths": ["src/index.ts"],
+ "relatedSymbols": [],
+ "signatures": [],
+ "members": [],
+ "structuredRows": []
+ },
+ {
+ "name": "OAuthFixtureDefinition",
+ "description": null,
+ "isReadme": false,
+ "examples": [],
+ "kind": "type",
+ "modulePath": "src/fixtures/oauth.ts",
+ "sourceLocation": {
+ "filePath": "src/fixtures/oauth.ts",
+ "line": 16,
+ "column": 1
+ },
+ "exportPaths": ["src/index.ts"],
+ "relatedSymbols": [],
+ "signatures": [],
+ "members": [
+ {
+ "name": "description",
+ "kind": "property",
+ "type": "string",
+ "required": true,
+ "description": null
+ },
+ {
+ "name": "id",
+ "kind": "property",
+ "type": "\"google\" | \"apple\" | \"google-apple\"",
+ "required": true,
+ "description": null
+ },
+ {
+ "name": "label",
+ "kind": "property",
+ "type": "string",
+ "required": true,
+ "description": null
+ },
+ {
+ "name": "oauth",
+ "kind": "property",
+ "type": "import(\"/home/runner/work/templates/templates/node_modules/@ankhorage/contracts/dist/auth\").AuthOAuthConfig",
+ "required": true,
+ "description": null
+ }
+ ],
+ "structuredRows": []
+ },
+ {
+ "name": "OAuthFixtureId",
+ "description": null,
+ "isReadme": false,
+ "examples": [],
+ "kind": "unknown",
+ "modulePath": "src/fixtures/oauth.ts",
+ "sourceLocation": {
+ "filePath": "src/fixtures/oauth.ts",
+ "line": 11,
+ "column": 1
+ },
+ "exportPaths": ["src/index.ts"],
+ "relatedSymbols": [],
+ "signatures": [],
+ "members": [],
+ "structuredRows": []
+ },
+ {
+ "name": "resolveOAuthFixture",
+ "description": null,
+ "isReadme": false,
+ "examples": [],
+ "kind": "function",
+ "modulePath": "src/fixtures/oauth.ts",
+ "sourceLocation": {
+ "filePath": "src/fixtures/oauth.ts",
+ "line": 83,
+ "column": 1
+ },
+ "exportPaths": ["src/index.ts"],
+ "relatedSymbols": ["OAuthFixtureDefinition"],
+ "signatures": [
+ {
+ "label": "(id: \"google\" | \"apple\" | \"google-apple\") => OAuthFixtureDefinition",
+ "parameters": [
+ {
+ "name": "id",
+ "type": "\"google\" | \"apple\" | \"google-apple\"",
+ "required": true,
+ "description": null
+ }
+ ],
+ "returnType": "OAuthFixtureDefinition",
+ "returnDescription": null
+ }
+ ],
+ "members": [],
+ "structuredRows": []
+ },
{
"name": "resolveStarterTemplate",
"description": null,
@@ -2777,65 +2991,83 @@
}
},
{
- "name": "resolveThemeModeValue",
+ "name": "createOAuthConfig",
"description": null,
"sourceLocation": {
- "filePath": "src/generators/create-category-app.ts",
- "line": 24,
+ "filePath": "src/fixtures/oauth.ts",
+ "line": 23,
"column": 1
}
},
{
- "name": "resolveSeedName",
+ "name": "listOAuthFixtures",
"description": null,
"sourceLocation": {
- "filePath": "src/generators/create-category-app.ts",
- "line": 33,
+ "filePath": "src/fixtures/oauth.ts",
+ "line": 79,
"column": 1
}
},
{
- "name": "resolveSeedSlug",
+ "name": "resolveOAuthFixture",
+ "description": null,
+ "sourceLocation": {
+ "filePath": "src/fixtures/oauth.ts",
+ "line": 83,
+ "column": 1
+ }
+ },
+ {
+ "name": "createOAuthFixtureManifest",
+ "description": null,
+ "sourceLocation": {
+ "filePath": "src/fixtures/oauth.ts",
+ "line": 87,
+ "column": 1
+ }
+ },
+ {
+ "name": "resolveThemeModeValue",
"description": null,
"sourceLocation": {
"filePath": "src/generators/create-category-app.ts",
- "line": 37,
+ "line": 8,
"column": 1
}
},
{
- "name": "resolveSeedThemeId",
+ "name": "resolveSeedName",
"description": null,
"sourceLocation": {
"filePath": "src/generators/create-category-app.ts",
- "line": 41,
+ "line": 17,
"column": 1
}
},
{
- "name": "createManifestFromTemplate",
+ "name": "resolveSeedSlug",
"description": null,
"sourceLocation": {
"filePath": "src/generators/create-category-app.ts",
- "line": 75,
+ "line": 21,
"column": 1
}
},
{
- "name": "addProviderEntryScreen",
+ "name": "resolveSeedThemeId",
"description": null,
"sourceLocation": {
"filePath": "src/generators/create-category-app.ts",
- "line": 83,
+ "line": 25,
"column": 1
}
},
{
- "name": "createProviderEntryScreen",
+ "name": "createManifestFromTemplate",
"description": null,
"sourceLocation": {
"filePath": "src/generators/create-category-app.ts",
- "line": 111,
+ "line": 59,
"column": 1
}
},
@@ -2844,7 +3076,7 @@
"description": null,
"sourceLocation": {
"filePath": "src/generators/create-category-app.ts",
- "line": 141,
+ "line": 67,
"column": 1
}
},
@@ -4432,6 +4664,14 @@
"description": null,
"isReadme": false
},
+ {
+ "kind": "export",
+ "name": "createOAuthFixtureManifest",
+ "sourcePath": "src/fixtures/oauth.ts",
+ "symbolName": "createOAuthFixtureManifest",
+ "description": null,
+ "isReadme": false
+ },
{
"kind": "export",
"name": "createStarterTemplate",
@@ -4440,6 +4680,14 @@
"description": null,
"isReadme": false
},
+ {
+ "kind": "export",
+ "name": "listOAuthFixtures",
+ "sourcePath": "src/fixtures/oauth.ts",
+ "symbolName": "listOAuthFixtures",
+ "description": null,
+ "isReadme": false
+ },
{
"kind": "export",
"name": "listStarterTemplates",
@@ -4464,6 +4712,14 @@
"description": null,
"isReadme": false
},
+ {
+ "kind": "export",
+ "name": "resolveOAuthFixture",
+ "sourcePath": "src/fixtures/oauth.ts",
+ "symbolName": "resolveOAuthFixture",
+ "description": null,
+ "isReadme": false
+ },
{
"kind": "export",
"name": "resolveStarterTemplate",
@@ -4535,6 +4791,11 @@
"toPath": "src/templateSelector.ts",
"sourcePath": "src/commands.ts"
},
+ {
+ "fromPath": "src/index.ts",
+ "toPath": "src/fixtures/oauth.ts",
+ "sourcePath": "src/index.ts"
+ },
{
"fromPath": "src/index.ts",
"toPath": "src/generators/create-category-app.ts",
@@ -4586,28 +4847,38 @@
"sourcePath": "src/cli/standalone.ts"
},
{
- "fromPath": "src/generators/create-category-app.ts",
+ "fromPath": "src/fixtures/oauth.ts",
+ "toPath": "src/generators/create-category-app.ts",
+ "sourcePath": "src/fixtures/oauth.ts"
+ },
+ {
+ "fromPath": "src/fixtures/oauth.ts",
"toPath": "src/internal/merge.ts",
- "sourcePath": "src/generators/create-category-app.ts"
+ "sourcePath": "src/fixtures/oauth.ts"
},
{
- "fromPath": "src/generators/create-category-app.ts",
+ "fromPath": "src/fixtures/oauth.ts",
"toPath": "src/internal/overrides.ts",
- "sourcePath": "src/generators/create-category-app.ts"
+ "sourcePath": "src/fixtures/oauth.ts"
+ },
+ {
+ "fromPath": "src/fixtures/oauth.ts",
+ "toPath": "src/templates/starter/index.ts",
+ "sourcePath": "src/fixtures/oauth.ts"
},
{
"fromPath": "src/generators/create-category-app.ts",
- "toPath": "src/presets/category-presets.ts",
+ "toPath": "src/internal/merge.ts",
"sourcePath": "src/generators/create-category-app.ts"
},
{
"fromPath": "src/generators/create-category-app.ts",
- "toPath": "src/templates/shared/screen.ts",
+ "toPath": "src/internal/overrides.ts",
"sourcePath": "src/generators/create-category-app.ts"
},
{
"fromPath": "src/generators/create-category-app.ts",
- "toPath": "src/templates/shared/zora-node-helpers.ts",
+ "toPath": "src/presets/category-presets.ts",
"sourcePath": "src/generators/create-category-app.ts"
},
{
@@ -6920,6 +7191,30 @@
"callExpression": "runCli",
"sourcePath": "src/cli/standalone.ts"
},
+ {
+ "fromSymbol": "src/fixtures/oauth.ts",
+ "toSymbol": "createOAuthConfig",
+ "callExpression": "createOAuthConfig",
+ "sourcePath": "src/fixtures/oauth.ts"
+ },
+ {
+ "fromSymbol": "createOAuthFixtureManifest",
+ "toSymbol": "createCategoryAppManifest",
+ "callExpression": "createCategoryAppManifest",
+ "sourcePath": "src/fixtures/oauth.ts"
+ },
+ {
+ "fromSymbol": "createOAuthFixtureManifest",
+ "toSymbol": "resolveOAuthFixture",
+ "callExpression": "resolveOAuthFixture",
+ "sourcePath": "src/fixtures/oauth.ts"
+ },
+ {
+ "fromSymbol": "createOAuthFixtureManifest",
+ "toSymbol": "mergeAppManifest",
+ "callExpression": "mergeAppManifest",
+ "sourcePath": "src/fixtures/oauth.ts"
+ },
{
"fromSymbol": "resolveThemeModeValue",
"toSymbol": "selector",
@@ -6962,54 +7257,12 @@
"callExpression": "TEMPLATE_FACTORIES[template]",
"sourcePath": "src/generators/create-category-app.ts"
},
- {
- "fromSymbol": "addProviderEntryScreen",
- "toSymbol": "resolveAuthFlow",
- "callExpression": "resolveAuthFlow",
- "sourcePath": "src/generators/create-category-app.ts"
- },
- {
- "fromSymbol": "addProviderEntryScreen",
- "toSymbol": "createProviderEntryScreen",
- "callExpression": "createProviderEntryScreen",
- "sourcePath": "src/generators/create-category-app.ts"
- },
- {
- "fromSymbol": "createProviderEntryScreen",
- "toSymbol": "createScreen",
- "callExpression": "createScreen",
- "sourcePath": "src/generators/create-category-app.ts"
- },
- {
- "fromSymbol": "createProviderEntryScreen",
- "toSymbol": "createScreenRoot",
- "callExpression": "createScreenRoot",
- "sourcePath": "src/generators/create-category-app.ts"
- },
- {
- "fromSymbol": "createProviderEntryScreen",
- "toSymbol": "createZoraNode",
- "callExpression": "createZoraNode",
- "sourcePath": "src/generators/create-category-app.ts"
- },
- {
- "fromSymbol": "createProviderEntryScreen",
- "toSymbol": "createSection",
- "callExpression": "createSection",
- "sourcePath": "src/generators/create-category-app.ts"
- },
{
"fromSymbol": "createCategoryAppManifest",
"toSymbol": "createManifestFromTemplate",
"callExpression": "createManifestFromTemplate",
"sourcePath": "src/generators/create-category-app.ts"
},
- {
- "fromSymbol": "createCategoryAppManifest",
- "toSymbol": "addProviderEntryScreen",
- "callExpression": "addProviderEntryScreen",
- "sourcePath": "src/generators/create-category-app.ts"
- },
{
"fromSymbol": "createCategoryAppManifest",
"toSymbol": "mergeAppManifest",
@@ -9112,6 +9365,46 @@
}
],
"typeReferences": [
+ {
+ "fromSymbol": "createOAuthFixtureManifest",
+ "toType": "AppCategory",
+ "sourcePath": "src/fixtures/oauth.ts"
+ },
+ {
+ "fromSymbol": "createOAuthFixtureManifest",
+ "toType": "AppManifest",
+ "sourcePath": "src/fixtures/oauth.ts"
+ },
+ {
+ "fromSymbol": "createOAuthFixtureManifest",
+ "toType": "AppManifestOverrides",
+ "sourcePath": "src/fixtures/oauth.ts"
+ },
+ {
+ "fromSymbol": "createOAuthFixtureManifest",
+ "toType": "OAuthFixtureId",
+ "sourcePath": "src/fixtures/oauth.ts"
+ },
+ {
+ "fromSymbol": "createOAuthFixtureManifest",
+ "toType": "TemplateKind",
+ "sourcePath": "src/fixtures/oauth.ts"
+ },
+ {
+ "fromSymbol": "listOAuthFixtures",
+ "toType": "OAuthFixtureDefinition",
+ "sourcePath": "src/fixtures/oauth.ts"
+ },
+ {
+ "fromSymbol": "OAuthFixtureDefinition",
+ "toType": "AuthOAuthConfig",
+ "sourcePath": "src/fixtures/oauth.ts"
+ },
+ {
+ "fromSymbol": "resolveOAuthFixture",
+ "toType": "OAuthFixtureDefinition",
+ "sourcePath": "src/fixtures/oauth.ts"
+ },
{
"fromSymbol": "createCategoryAppManifest",
"toType": "AdapterId",
diff --git a/package.json b/package.json
index 4d2a39a..02b49b0 100644
--- a/package.json
+++ b/package.json
@@ -74,7 +74,7 @@
"version-packages": "changeset version"
},
"dependencies": {
- "@ankhorage/contracts": "^2.1.0",
+ "@ankhorage/contracts": "^3.0.0",
"@ankhorage/zora": "^2.8.0"
},
"devDependencies": {
diff --git a/src/fixtures/oauth.ts b/src/fixtures/oauth.ts
new file mode 100644
index 0000000..7e42567
--- /dev/null
+++ b/src/fixtures/oauth.ts
@@ -0,0 +1,107 @@
+import type { AppCategory, AppManifest } from '@ankhorage/contracts';
+
+import { createCategoryAppManifest } from '../generators/create-category-app';
+import { mergeAppManifest } from '../internal/merge';
+import type { AppManifestOverrides } from '../internal/overrides';
+import type { TemplateKind } from '../templates/starter';
+
+export const OAUTH_CALLBACK_ROUTE = 'auth/callback';
+export const OAUTH_FIXTURE_IDS = ['google', 'apple', 'google-apple'] as const;
+
+export type OAuthFixtureId = (typeof OAUTH_FIXTURE_IDS)[number];
+
+type OAuthConfig = NonNullable['oauth']>;
+type OAuthProviderConfig = OAuthConfig['providers'][number];
+
+export interface OAuthFixtureDefinition {
+ readonly id: OAuthFixtureId;
+ readonly label: string;
+ readonly description: string;
+ readonly oauth: OAuthConfig;
+}
+
+function createOAuthConfig(providers: readonly OAuthProviderConfig[]): OAuthConfig {
+ return {
+ enabled: true,
+ callbackRoute: OAUTH_CALLBACK_ROUTE,
+ providers: providers.map((provider) => structuredClone(provider)),
+ };
+}
+
+const GOOGLE_PROVIDER: OAuthProviderConfig = {
+ id: 'google',
+ label: 'Continue with Google',
+ enabled: true,
+ credentialsRef: 'auth/oauth/google',
+ scopes: ['openid', 'email', 'profile'],
+ queryParams: {
+ prompt: 'select_account',
+ },
+ icon: {
+ provider: 'FontAwesome',
+ name: 'google',
+ },
+};
+
+const APPLE_PROVIDER: OAuthProviderConfig = {
+ id: 'apple',
+ label: 'Continue with Apple',
+ enabled: true,
+ credentialsRef: 'auth/oauth/apple',
+ scopes: ['name', 'email'],
+ icon: {
+ provider: 'FontAwesome',
+ name: 'apple',
+ },
+};
+
+const FIXTURES: Record = {
+ google: {
+ id: 'google',
+ label: 'Google OAuth',
+ description: 'Canonical Google authorization-code-with-PKCE fixture.',
+ oauth: createOAuthConfig([GOOGLE_PROVIDER]),
+ },
+ apple: {
+ id: 'apple',
+ label: 'Apple OAuth',
+ description: 'Canonical Apple authorization-code-with-PKCE fixture.',
+ oauth: createOAuthConfig([APPLE_PROVIDER]),
+ },
+ 'google-apple': {
+ id: 'google-apple',
+ label: 'Google and Apple OAuth',
+ description: 'Canonical combined Google and Apple authorization-code-with-PKCE fixture.',
+ oauth: createOAuthConfig([GOOGLE_PROVIDER, APPLE_PROVIDER]),
+ },
+};
+
+export function listOAuthFixtures(): OAuthFixtureDefinition[] {
+ return OAUTH_FIXTURE_IDS.map((id) => structuredClone(FIXTURES[id]));
+}
+
+export function resolveOAuthFixture(id: OAuthFixtureId): OAuthFixtureDefinition {
+ return structuredClone(FIXTURES[id]);
+}
+
+export function createOAuthFixtureManifest(args: {
+ category: AppCategory;
+ fixture: OAuthFixtureId;
+ template?: TemplateKind;
+ overrides?: AppManifestOverrides;
+}): AppManifest {
+ const manifest = createCategoryAppManifest(
+ args.category,
+ args.template ?? 'starter',
+ args.overrides,
+ );
+ const fixture = resolveOAuthFixture(args.fixture);
+
+ return mergeAppManifest(manifest, {
+ infra: {
+ auth: {
+ oauth: fixture.oauth,
+ },
+ },
+ });
+}
diff --git a/src/generators/create-category-app.ts b/src/generators/create-category-app.ts
index 2a110f9..7a13bf9 100644
--- a/src/generators/create-category-app.ts
+++ b/src/generators/create-category-app.ts
@@ -1,26 +1,10 @@
-import {
- type AppCategory,
- type AppManifest,
- resolveAuthFlow,
- type ScreenSpec,
- type ThemeConfig,
-} from '@ankhorage/contracts';
+import type { AppCategory, AppManifest, ThemeConfig } from '@ankhorage/contracts';
import { mergeAppManifest } from '../internal/merge';
import type { AppManifestOverrides } from '../internal/overrides';
import { CATEGORY_PRESETS } from '../presets/category-presets';
-import { createScreen } from '../templates/shared/screen';
-import {
- createScreenRoot,
- createSection,
- createZoraNode,
-} from '../templates/shared/zora-node-helpers';
import { createStarterTemplate, type TemplateKind } from '../templates/starter';
-type ProviderConfig = NonNullable<
- NonNullable['oauth']
->['providers'][number];
-
function resolveThemeModeValue(
overrides: AppManifestOverrides | undefined,
selector: (theme: ThemeConfig) => TValue,
@@ -80,69 +64,11 @@ function createManifestFromTemplate(
return TEMPLATE_FACTORIES[template](category, overrides);
}
-function addProviderEntryScreen(manifest: AppManifest): AppManifest {
- const providers = manifest.infra.auth?.oauth?.providers.filter(
- (provider) => provider.enabled !== false,
- );
-
- if (
- manifest.infra.auth?.oauth?.enabled !== true ||
- providers === undefined ||
- providers.length === 0
- ) {
- return manifest;
- }
-
- const screenId = resolveAuthFlow(manifest.infra.auth.flow).signInRoute;
-
- if (manifest.screens[screenId] !== undefined) {
- return manifest;
- }
-
- return {
- ...manifest,
- screens: {
- ...manifest.screens,
- [screenId]: createProviderEntryScreen(screenId, providers),
- },
- };
-}
-
-function createProviderEntryScreen(
- screenId: string,
- providers: readonly ProviderConfig[],
-): ScreenSpec {
- return createScreen({
- id: screenId,
- name: 'Provider entry',
- title: 'Provider entry',
- description: 'Choose a provider.',
- root: createScreenRoot('provider-entry-screen', { width: 'default' }, [
- createZoraNode('provider-entry-header', 'SectionHeader', {
- eyebrow: 'Account',
- title: 'Continue',
- description: 'Choose a configured provider.',
- }),
- createSection('provider-entry-section', { title: 'Providers' }, [
- createZoraNode('provider-entry-list', 'OAuthProviderList', {
- providers: providers.map((provider) => ({
- id: provider.id,
- ...(provider.label ? { label: provider.label } : {}),
- ...(provider.icon ? { icon: provider.icon } : {}),
- })),
- layout: 'stack',
- fullWidth: true,
- }),
- ]),
- ]),
- });
-}
-
export function createCategoryAppManifest(
category: AppCategory,
template: TemplateKind = 'starter',
overrides?: AppManifestOverrides,
): AppManifest {
const manifest = createManifestFromTemplate(category, template, overrides);
- return addProviderEntryScreen(mergeAppManifest(manifest, overrides));
+ return mergeAppManifest(manifest, overrides);
}
diff --git a/src/index.ts b/src/index.ts
index 2268c0c..ada16e1 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,12 @@
+export {
+ createOAuthFixtureManifest,
+ listOAuthFixtures,
+ OAUTH_CALLBACK_ROUTE,
+ OAUTH_FIXTURE_IDS,
+ type OAuthFixtureDefinition,
+ type OAuthFixtureId,
+ resolveOAuthFixture,
+} from './fixtures/oauth';
export { createCategoryAppManifest } from './generators/create-category-app';
export { CATEGORY_PRESETS, type CategoryPreset } from './presets/category-presets';
export {
diff --git a/test/oauth-auth-templates.test.ts b/test/oauth-auth-templates.test.ts
index 9d6c967..f3105de 100644
--- a/test/oauth-auth-templates.test.ts
+++ b/test/oauth-auth-templates.test.ts
@@ -1,108 +1,117 @@
-import type { AppManifest, UiNode } from '@ankhorage/contracts';
-import { resolveAuthFlow } from '@ankhorage/contracts/auth';
+import { APP_CATEGORIES, resolveAuthFlow } from '@ankhorage/contracts';
import { describe, expect, test } from 'bun:test';
-import { createCategoryAppManifest } from '../src/index';
+import {
+ createCategoryAppManifest,
+ createOAuthFixtureManifest,
+ listOAuthFixtures,
+ OAUTH_CALLBACK_ROUTE,
+ OAUTH_FIXTURE_IDS,
+ resolveOAuthFixture,
+} from '../src/index';
-function collectNodes(node: UiNode): UiNode[] {
- return [node, ...(node.children?.flatMap(collectNodes) ?? [])];
-}
+const SECRET_SENTINEL = 'sentinel-phase3-template-secret-do-not-leak';
-function createOauthManifest() {
- return createCategoryAppManifest('developer_tools', 'starter', {
- infra: {
- auth: {
- oauth: {
- enabled: true,
- callbackRoute: '/auth/callback',
- providers: [
- {
- id: 'google',
- label: 'Google',
- enabled: true,
- icon: { provider: 'FontAwesome', name: 'google' },
- },
- {
- id: 'github',
- label: 'GitHub',
- enabled: false,
- icon: { provider: 'FontAwesome', name: 'github' },
- },
- {
- id: 'custom-sso',
- label: 'Custom SSO',
- enabled: true,
- },
- ],
- },
- },
- },
- });
+function serialize(value: unknown): string {
+ return JSON.stringify(value);
}
-describe('OAuth auth template generation', () => {
- test('adds an OAuth provider entry screen outside navigation when enabled', () => {
- const manifest = createOauthManifest();
- const { signInRoute } = resolveAuthFlow(manifest.infra.auth?.flow);
- const screen = manifest.screens[signInRoute];
-
- expect(screen).toBeDefined();
- expect(manifest.navigator.routes.map((route) => route.name)).not.toContain(signInRoute);
-
- const nodes = screen ? collectNodes(screen.root) : [];
- const providerList = nodes.find((node) => node.type === 'OAuthProviderList');
-
- expect(providerList?.props).toEqual({
- providers: [
- {
- id: 'google',
- label: 'Google',
- icon: { provider: 'FontAwesome', name: 'google' },
- },
- {
- id: 'custom-sso',
- label: 'Custom SSO',
- },
- ],
- layout: 'stack',
- fullWidth: true,
- });
- });
-
- test('does not add an OAuth provider entry screen when OAuth is disabled', () => {
+describe('canonical OAuth template fixtures', () => {
+ test('does not create template-owned OAuth auth screens', () => {
const manifest = createCategoryAppManifest('developer_tools', 'starter', {
- infra: {
- auth: {
- oauth: {
- enabled: false,
- callbackRoute: '/auth/callback',
- providers: [{ id: 'google', label: 'Google' }],
- },
- },
- },
- });
-
- const { signInRoute } = resolveAuthFlow(manifest.infra.auth?.flow);
- expect(manifest.screens[signInRoute]).toBeUndefined();
- });
-
- test('does not add an OAuth provider entry screen when all providers are disabled', () => {
- const manifest: AppManifest = createCategoryAppManifest('developer_tools', 'starter', {
infra: {
auth: {
oauth: {
enabled: true,
- callbackRoute: '/auth/callback',
+ callbackRoute: OAUTH_CALLBACK_ROUTE,
providers: [
- { id: 'google', label: 'Google', enabled: false },
- { id: 'github', label: 'GitHub', enabled: false },
+ {
+ id: 'google',
+ enabled: true,
+ credentialsRef: 'auth/oauth/google',
+ },
],
},
},
},
});
-
const { signInRoute } = resolveAuthFlow(manifest.infra.auth?.flow);
+
expect(manifest.screens[signInRoute]).toBeUndefined();
+ expect(manifest.navigator.routes.map((route) => route.name)).not.toContain(signInRoute);
+ expect(serialize(manifest)).not.toContain('OAuthProviderList');
+ });
+
+ test('publishes deterministic Google, Apple, and combined fixtures', () => {
+ expect(OAUTH_FIXTURE_IDS).toEqual(['google', 'apple', 'google-apple']);
+ expect(listOAuthFixtures().map((fixture) => fixture.id)).toEqual([...OAUTH_FIXTURE_IDS]);
+ expect(resolveOAuthFixture('google').oauth.providers.map((provider) => provider.id)).toEqual([
+ 'google',
+ ]);
+ expect(resolveOAuthFixture('apple').oauth.providers.map((provider) => provider.id)).toEqual([
+ 'apple',
+ ]);
+ expect(
+ resolveOAuthFixture('google-apple').oauth.providers.map((provider) => provider.id),
+ ).toEqual(['google', 'apple']);
+ });
+
+ test('uses one canonical callback and logical credential references only', () => {
+ for (const fixture of listOAuthFixtures()) {
+ expect(fixture.oauth.enabled).toBe(true);
+ expect(fixture.oauth.callbackRoute).toBe('auth/callback');
+ expect(fixture.oauth.callbackRoute.startsWith('/')).toBe(false);
+ expect(fixture.oauth.providers.length).toBeGreaterThan(0);
+
+ for (const provider of fixture.oauth.providers) {
+ expect(['google', 'apple']).toContain(provider.id);
+ expect(provider.enabled).toBe(true);
+ expect(provider.credentialsRef).toBe(`auth/oauth/${provider.id}`);
+ expect(provider.label).toBe(
+ `Continue with ${provider.id === 'google' ? 'Google' : 'Apple'}`,
+ );
+ }
+
+ const serialized = serialize(fixture);
+ expect(serialized).not.toContain(SECRET_SENTINEL);
+ expect(serialized).not.toContain('clientSecret');
+ expect(serialized).not.toContain('privateKey');
+ expect(serialized).not.toContain('serviceRoleKey');
+ expect(serialized).not.toContain('accessToken');
+ expect(serialized).not.toContain('refreshToken');
+ }
+ });
+
+ test('creates OAuth fixture manifests without changing canonical auth flow', () => {
+ for (const category of APP_CATEGORIES) {
+ const manifest = createOAuthFixtureManifest({
+ category,
+ fixture: 'google-apple',
+ });
+ const flow = resolveAuthFlow(manifest.infra.auth?.flow);
+
+ expect(flow.signInRoute).toBe('sign-in');
+ expect(flow.signUpRoute).toBe('sign-up');
+ expect(flow.signOutRoute).toBe('sign-out');
+ expect(flow.postSignInRoute).toBe('/');
+ expect(manifest.infra.auth?.oauth?.callbackRoute).toBe(OAUTH_CALLBACK_ROUTE);
+ expect(manifest.infra.auth?.oauth?.providers.map((provider) => provider.id)).toEqual([
+ 'google',
+ 'apple',
+ ]);
+ expect(serialize(manifest)).not.toContain('OAuthProviderList');
+ }
+ });
+
+ test('returns isolated fixture definitions', () => {
+ const fixture = resolveOAuthFixture('google');
+ const [provider] = fixture.oauth.providers;
+ if (!provider) {
+ throw new Error('Expected the Google OAuth fixture to contain one provider.');
+ }
+ provider.label = SECRET_SENTINEL;
+
+ expect(resolveOAuthFixture('google').oauth.providers[0]?.label).toBe('Continue with Google');
+ expect(serialize(listOAuthFixtures())).not.toContain(SECRET_SENTINEL);
});
});
diff --git a/test/secret-store-default.test.ts b/test/secret-store-default.test.ts
index 45087af..99c6e69 100644
--- a/test/secret-store-default.test.ts
+++ b/test/secret-store-default.test.ts
@@ -22,7 +22,7 @@ describe('canonical secret-store template default', () => {
auth: {
oauth: {
enabled: true,
- callbackRoute: '/auth/callback',
+ callbackRoute: 'auth/callback',
providers: [
{
id: 'google',