@atlasauth/cli 0.23.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/package.json +3 -3
- package/dist/args.d.ts +0 -601
- package/dist/args.js +0 -289
- package/dist/args.js.map +0 -1
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -19
- package/dist/cli.js.map +0 -1
- package/dist/commands/actions-gallery.d.ts +0 -8
- package/dist/commands/actions-gallery.js +0 -197
- package/dist/commands/actions-gallery.js.map +0 -1
- package/dist/commands/actions.d.ts +0 -13
- package/dist/commands/actions.js +0 -135
- package/dist/commands/actions.js.map +0 -1
- package/dist/commands/admin.d.ts +0 -49
- package/dist/commands/admin.js +0 -353
- package/dist/commands/admin.js.map +0 -1
- package/dist/commands/api-keys.d.ts +0 -4
- package/dist/commands/api-keys.js +0 -96
- package/dist/commands/api-keys.js.map +0 -1
- package/dist/commands/auth.d.ts +0 -25
- package/dist/commands/auth.js +0 -102
- package/dist/commands/auth.js.map +0 -1
- package/dist/commands/config.d.ts +0 -90
- package/dist/commands/config.js +0 -1430
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/csp.d.ts +0 -32
- package/dist/commands/csp.js +0 -180
- package/dist/commands/csp.js.map +0 -1
- package/dist/commands/dev.d.ts +0 -8
- package/dist/commands/dev.js +0 -84
- package/dist/commands/dev.js.map +0 -1
- package/dist/commands/fga.d.ts +0 -10
- package/dist/commands/fga.js +0 -146
- package/dist/commands/fga.js.map +0 -1
- package/dist/commands/golive.d.ts +0 -40
- package/dist/commands/golive.js +0 -285
- package/dist/commands/golive.js.map +0 -1
- package/dist/commands/id-accounts.d.ts +0 -13
- package/dist/commands/id-accounts.js +0 -195
- package/dist/commands/id-accounts.js.map +0 -1
- package/dist/commands/import-better-auth.d.ts +0 -3
- package/dist/commands/import-better-auth.js +0 -164
- package/dist/commands/import-better-auth.js.map +0 -1
- package/dist/commands/import.d.ts +0 -3
- package/dist/commands/import.js +0 -123
- package/dist/commands/import.js.map +0 -1
- package/dist/commands/info.d.ts +0 -75
- package/dist/commands/info.js +0 -362
- package/dist/commands/info.js.map +0 -1
- package/dist/commands/init.d.ts +0 -3
- package/dist/commands/init.js +0 -438
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/integrations.d.ts +0 -8
- package/dist/commands/integrations.js +0 -112
- package/dist/commands/integrations.js.map +0 -1
- package/dist/commands/listen.d.ts +0 -17
- package/dist/commands/listen.js +0 -223
- package/dist/commands/listen.js.map +0 -1
- package/dist/commands/mcp.d.ts +0 -38
- package/dist/commands/mcp.js +0 -111
- package/dist/commands/mcp.js.map +0 -1
- package/dist/commands/migrate.d.ts +0 -16
- package/dist/commands/migrate.js +0 -114
- package/dist/commands/migrate.js.map +0 -1
- package/dist/commands/move-origin.d.ts +0 -11
- package/dist/commands/move-origin.js +0 -95
- package/dist/commands/move-origin.js.map +0 -1
- package/dist/commands/operator.d.ts +0 -18
- package/dist/commands/operator.js +0 -238
- package/dist/commands/operator.js.map +0 -1
- package/dist/commands/orgs.d.ts +0 -4
- package/dist/commands/orgs.js +0 -72
- package/dist/commands/orgs.js.map +0 -1
- package/dist/commands/pages.d.ts +0 -14
- package/dist/commands/pages.js +0 -113
- package/dist/commands/pages.js.map +0 -1
- package/dist/commands/partner-platforms.d.ts +0 -10
- package/dist/commands/partner-platforms.js +0 -157
- package/dist/commands/partner-platforms.js.map +0 -1
- package/dist/commands/project-commands.d.ts +0 -38
- package/dist/commands/project-commands.js +0 -361
- package/dist/commands/project-commands.js.map +0 -1
- package/dist/commands/referrals.d.ts +0 -69
- package/dist/commands/referrals.js +0 -1056
- package/dist/commands/referrals.js.map +0 -1
- package/dist/commands/resources.d.ts +0 -44
- package/dist/commands/resources.js +0 -234
- package/dist/commands/resources.js.map +0 -1
- package/dist/commands/sessions.d.ts +0 -4
- package/dist/commands/sessions.js +0 -28
- package/dist/commands/sessions.js.map +0 -1
- package/dist/commands/shared.d.ts +0 -38
- package/dist/commands/shared.js +0 -69
- package/dist/commands/shared.js.map +0 -1
- package/dist/commands/status.d.ts +0 -3
- package/dist/commands/status.js +0 -77
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/users.d.ts +0 -4
- package/dist/commands/users.js +0 -100
- package/dist/commands/users.js.map +0 -1
- package/dist/commands/webhooks.d.ts +0 -34
- package/dist/commands/webhooks.js +0 -301
- package/dist/commands/webhooks.js.map +0 -1
- package/dist/config-http.d.ts +0 -29
- package/dist/config-http.js +0 -64
- package/dist/config-http.js.map +0 -1
- package/dist/config.d.ts +0 -64
- package/dist/config.js +0 -69
- package/dist/config.js.map +0 -1
- package/dist/context.d.ts +0 -72
- package/dist/context.js +0 -27
- package/dist/context.js.map +0 -1
- package/dist/errors.d.ts +0 -15
- package/dist/errors.js +0 -25
- package/dist/errors.js.map +0 -1
- package/dist/help.d.ts +0 -8
- package/dist/help.js +0 -868
- package/dist/help.js.map +0 -1
- package/dist/import/csv.d.ts +0 -6
- package/dist/import/csv.js +0 -63
- package/dist/import/csv.js.map +0 -1
- package/dist/import/vendors.d.ts +0 -57
- package/dist/import/vendors.js +0 -568
- package/dist/import/vendors.js.map +0 -1
- package/dist/index.d.ts +0 -32
- package/dist/index.js +0 -79
- package/dist/index.js.map +0 -1
- package/dist/io.d.ts +0 -21
- package/dist/io.js +0 -28
- package/dist/io.js.map +0 -1
- package/dist/migrate/appwrite/plugin.d.ts +0 -2
- package/dist/migrate/appwrite/plugin.js +0 -103
- package/dist/migrate/appwrite/plugin.js.map +0 -1
- package/dist/migrate/auth0/plugin.d.ts +0 -2
- package/dist/migrate/auth0/plugin.js +0 -126
- package/dist/migrate/auth0/plugin.js.map +0 -1
- package/dist/migrate/authentik/plugin.d.ts +0 -2
- package/dist/migrate/authentik/plugin.js +0 -135
- package/dist/migrate/authentik/plugin.js.map +0 -1
- package/dist/migrate/azure-ad-b2c/plugin.d.ts +0 -8
- package/dist/migrate/azure-ad-b2c/plugin.js +0 -165
- package/dist/migrate/azure-ad-b2c/plugin.js.map +0 -1
- package/dist/migrate/better-auth/integration.d.ts +0 -59
- package/dist/migrate/better-auth/integration.js +0 -81
- package/dist/migrate/better-auth/integration.js.map +0 -1
- package/dist/migrate/better-auth/orchestrator.d.ts +0 -43
- package/dist/migrate/better-auth/orchestrator.js +0 -525
- package/dist/migrate/better-auth/orchestrator.js.map +0 -1
- package/dist/migrate/better-auth/output.d.ts +0 -20
- package/dist/migrate/better-auth/output.js +0 -146
- package/dist/migrate/better-auth/output.js.map +0 -1
- package/dist/migrate/better-auth/translate.d.ts +0 -77
- package/dist/migrate/better-auth/translate.js +0 -1989
- package/dist/migrate/better-auth/translate.js.map +0 -1
- package/dist/migrate/clerk/plugin.d.ts +0 -2
- package/dist/migrate/clerk/plugin.js +0 -76
- package/dist/migrate/clerk/plugin.js.map +0 -1
- package/dist/migrate/cognito/plugin.d.ts +0 -5
- package/dist/migrate/cognito/plugin.js +0 -170
- package/dist/migrate/cognito/plugin.js.map +0 -1
- package/dist/migrate/corbado/plugin.d.ts +0 -2
- package/dist/migrate/corbado/plugin.js +0 -63
- package/dist/migrate/corbado/plugin.js.map +0 -1
- package/dist/migrate/descope/plugin.d.ts +0 -2
- package/dist/migrate/descope/plugin.js +0 -83
- package/dist/migrate/descope/plugin.js.map +0 -1
- package/dist/migrate/entra-external-id/plugin.d.ts +0 -8
- package/dist/migrate/entra-external-id/plugin.js +0 -12
- package/dist/migrate/entra-external-id/plugin.js.map +0 -1
- package/dist/migrate/export/appwrite-config.d.ts +0 -28
- package/dist/migrate/export/appwrite-config.js +0 -170
- package/dist/migrate/export/appwrite-config.js.map +0 -1
- package/dist/migrate/export/appwrite.d.ts +0 -51
- package/dist/migrate/export/appwrite.js +0 -288
- package/dist/migrate/export/appwrite.js.map +0 -1
- package/dist/migrate/export/auth0-config.d.ts +0 -29
- package/dist/migrate/export/auth0-config.js +0 -249
- package/dist/migrate/export/auth0-config.js.map +0 -1
- package/dist/migrate/export/auth0.d.ts +0 -62
- package/dist/migrate/export/auth0.js +0 -575
- package/dist/migrate/export/auth0.js.map +0 -1
- package/dist/migrate/export/authentik-config.d.ts +0 -45
- package/dist/migrate/export/authentik-config.js +0 -357
- package/dist/migrate/export/authentik-config.js.map +0 -1
- package/dist/migrate/export/authentik.d.ts +0 -110
- package/dist/migrate/export/authentik.js +0 -838
- package/dist/migrate/export/authentik.js.map +0 -1
- package/dist/migrate/export/azure-config.d.ts +0 -58
- package/dist/migrate/export/azure-config.js +0 -344
- package/dist/migrate/export/azure-config.js.map +0 -1
- package/dist/migrate/export/azure.d.ts +0 -85
- package/dist/migrate/export/azure.js +0 -373
- package/dist/migrate/export/azure.js.map +0 -1
- package/dist/migrate/export/clerk-config.d.ts +0 -26
- package/dist/migrate/export/clerk-config.js +0 -227
- package/dist/migrate/export/clerk-config.js.map +0 -1
- package/dist/migrate/export/clerk.d.ts +0 -46
- package/dist/migrate/export/clerk.js +0 -486
- package/dist/migrate/export/clerk.js.map +0 -1
- package/dist/migrate/export/cognito-config.d.ts +0 -25
- package/dist/migrate/export/cognito-config.js +0 -273
- package/dist/migrate/export/cognito-config.js.map +0 -1
- package/dist/migrate/export/cognito.d.ts +0 -103
- package/dist/migrate/export/cognito.js +0 -441
- package/dist/migrate/export/cognito.js.map +0 -1
- package/dist/migrate/export/corbado-config.d.ts +0 -20
- package/dist/migrate/export/corbado-config.js +0 -63
- package/dist/migrate/export/corbado-config.js.map +0 -1
- package/dist/migrate/export/corbado.d.ts +0 -37
- package/dist/migrate/export/corbado.js +0 -132
- package/dist/migrate/export/corbado.js.map +0 -1
- package/dist/migrate/export/descope.d.ts +0 -35
- package/dist/migrate/export/descope.js +0 -208
- package/dist/migrate/export/descope.js.map +0 -1
- package/dist/migrate/export/fake-vendor.test-helper.d.ts +0 -27
- package/dist/migrate/export/fake-vendor.test-helper.js +0 -41
- package/dist/migrate/export/fake-vendor.test-helper.js.map +0 -1
- package/dist/migrate/export/firebase-config.d.ts +0 -24
- package/dist/migrate/export/firebase-config.js +0 -215
- package/dist/migrate/export/firebase-config.js.map +0 -1
- package/dist/migrate/export/firebase.d.ts +0 -102
- package/dist/migrate/export/firebase.js +0 -464
- package/dist/migrate/export/firebase.js.map +0 -1
- package/dist/migrate/export/frameworks/app-users.d.ts +0 -42
- package/dist/migrate/export/frameworks/app-users.js +0 -161
- package/dist/migrate/export/frameworks/app-users.js.map +0 -1
- package/dist/migrate/export/frameworks/authjs.d.ts +0 -41
- package/dist/migrate/export/frameworks/authjs.js +0 -146
- package/dist/migrate/export/frameworks/authjs.js.map +0 -1
- package/dist/migrate/export/frameworks/devise.d.ts +0 -39
- package/dist/migrate/export/frameworks/devise.js +0 -218
- package/dist/migrate/export/frameworks/devise.js.map +0 -1
- package/dist/migrate/export/frameworks/django.d.ts +0 -45
- package/dist/migrate/export/frameworks/django.js +0 -315
- package/dist/migrate/export/frameworks/django.js.map +0 -1
- package/dist/migrate/export/frameworks/lucia.d.ts +0 -42
- package/dist/migrate/export/frameworks/lucia.js +0 -263
- package/dist/migrate/export/frameworks/lucia.js.map +0 -1
- package/dist/migrate/export/frameworks/tables.d.ts +0 -90
- package/dist/migrate/export/frameworks/tables.js +0 -325
- package/dist/migrate/export/frameworks/tables.js.map +0 -1
- package/dist/migrate/export/frameworks/webauthn.d.ts +0 -25
- package/dist/migrate/export/frameworks/webauthn.js +0 -130
- package/dist/migrate/export/frameworks/webauthn.js.map +0 -1
- package/dist/migrate/export/frontegg-config.d.ts +0 -29
- package/dist/migrate/export/frontegg-config.js +0 -238
- package/dist/migrate/export/frontegg-config.js.map +0 -1
- package/dist/migrate/export/frontegg.d.ts +0 -67
- package/dist/migrate/export/frontegg.js +0 -410
- package/dist/migrate/export/frontegg.js.map +0 -1
- package/dist/migrate/export/fusionauth-config.d.ts +0 -26
- package/dist/migrate/export/fusionauth-config.js +0 -226
- package/dist/migrate/export/fusionauth-config.js.map +0 -1
- package/dist/migrate/export/fusionauth.d.ts +0 -99
- package/dist/migrate/export/fusionauth.js +0 -642
- package/dist/migrate/export/fusionauth.js.map +0 -1
- package/dist/migrate/export/hanko-config.d.ts +0 -27
- package/dist/migrate/export/hanko-config.js +0 -265
- package/dist/migrate/export/hanko-config.js.map +0 -1
- package/dist/migrate/export/hanko.d.ts +0 -52
- package/dist/migrate/export/hanko.js +0 -605
- package/dist/migrate/export/hanko.js.map +0 -1
- package/dist/migrate/export/keycloak-config.d.ts +0 -19
- package/dist/migrate/export/keycloak-config.js +0 -219
- package/dist/migrate/export/keycloak-config.js.map +0 -1
- package/dist/migrate/export/keycloak.d.ts +0 -155
- package/dist/migrate/export/keycloak.js +0 -878
- package/dist/migrate/export/keycloak.js.map +0 -1
- package/dist/migrate/export/kinde-descope-config.d.ts +0 -38
- package/dist/migrate/export/kinde-descope-config.js +0 -172
- package/dist/migrate/export/kinde-descope-config.js.map +0 -1
- package/dist/migrate/export/kinde.d.ts +0 -57
- package/dist/migrate/export/kinde.js +0 -333
- package/dist/migrate/export/kinde.js.map +0 -1
- package/dist/migrate/export/ldap.d.ts +0 -53
- package/dist/migrate/export/ldap.js +0 -277
- package/dist/migrate/export/ldap.js.map +0 -1
- package/dist/migrate/export/logto-config.d.ts +0 -27
- package/dist/migrate/export/logto-config.js +0 -264
- package/dist/migrate/export/logto-config.js.map +0 -1
- package/dist/migrate/export/logto.d.ts +0 -150
- package/dist/migrate/export/logto.js +0 -809
- package/dist/migrate/export/logto.js.map +0 -1
- package/dist/migrate/export/magic-config.d.ts +0 -14
- package/dist/migrate/export/magic-config.js +0 -28
- package/dist/migrate/export/magic-config.js.map +0 -1
- package/dist/migrate/export/magic.d.ts +0 -31
- package/dist/migrate/export/magic.js +0 -93
- package/dist/migrate/export/magic.js.map +0 -1
- package/dist/migrate/export/mini-yaml.d.ts +0 -19
- package/dist/migrate/export/mini-yaml.js +0 -581
- package/dist/migrate/export/mini-yaml.js.map +0 -1
- package/dist/migrate/export/nhost-config.d.ts +0 -26
- package/dist/migrate/export/nhost-config.js +0 -360
- package/dist/migrate/export/nhost-config.js.map +0 -1
- package/dist/migrate/export/nhost.d.ts +0 -64
- package/dist/migrate/export/nhost.js +0 -304
- package/dist/migrate/export/nhost.js.map +0 -1
- package/dist/migrate/export/okta-config.d.ts +0 -30
- package/dist/migrate/export/okta-config.js +0 -205
- package/dist/migrate/export/okta-config.js.map +0 -1
- package/dist/migrate/export/okta.d.ts +0 -43
- package/dist/migrate/export/okta.js +0 -337
- package/dist/migrate/export/okta.js.map +0 -1
- package/dist/migrate/export/onelogin.d.ts +0 -22
- package/dist/migrate/export/onelogin.js +0 -123
- package/dist/migrate/export/onelogin.js.map +0 -1
- package/dist/migrate/export/ory-config.d.ts +0 -58
- package/dist/migrate/export/ory-config.js +0 -451
- package/dist/migrate/export/ory-config.js.map +0 -1
- package/dist/migrate/export/ory.d.ts +0 -81
- package/dist/migrate/export/ory.js +0 -555
- package/dist/migrate/export/ory.js.map +0 -1
- package/dist/migrate/export/password-detect.d.ts +0 -29
- package/dist/migrate/export/password-detect.js +0 -81
- package/dist/migrate/export/password-detect.js.map +0 -1
- package/dist/migrate/export/pingone.d.ts +0 -36
- package/dist/migrate/export/pingone.js +0 -218
- package/dist/migrate/export/pingone.js.map +0 -1
- package/dist/migrate/export/plugin-kit.d.ts +0 -25
- package/dist/migrate/export/plugin-kit.js +0 -82
- package/dist/migrate/export/plugin-kit.js.map +0 -1
- package/dist/migrate/export/pocketbase-config.d.ts +0 -28
- package/dist/migrate/export/pocketbase-config.js +0 -180
- package/dist/migrate/export/pocketbase-config.js.map +0 -1
- package/dist/migrate/export/pocketbase.d.ts +0 -52
- package/dist/migrate/export/pocketbase.js +0 -243
- package/dist/migrate/export/pocketbase.js.map +0 -1
- package/dist/migrate/export/project-files.d.ts +0 -15
- package/dist/migrate/export/project-files.js +0 -43
- package/dist/migrate/export/project-files.js.map +0 -1
- package/dist/migrate/export/propelauth-config.d.ts +0 -18
- package/dist/migrate/export/propelauth-config.js +0 -58
- package/dist/migrate/export/propelauth-config.js.map +0 -1
- package/dist/migrate/export/propelauth-harness.test-helper.d.ts +0 -62
- package/dist/migrate/export/propelauth-harness.test-helper.js +0 -109
- package/dist/migrate/export/propelauth-harness.test-helper.js.map +0 -1
- package/dist/migrate/export/propelauth.d.ts +0 -48
- package/dist/migrate/export/propelauth.js +0 -255
- package/dist/migrate/export/propelauth.js.map +0 -1
- package/dist/migrate/export/record-set.d.ts +0 -44
- package/dist/migrate/export/record-set.js +0 -98
- package/dist/migrate/export/record-set.js.map +0 -1
- package/dist/migrate/export/source-lines.d.ts +0 -17
- package/dist/migrate/export/source-lines.js +0 -43
- package/dist/migrate/export/source-lines.js.map +0 -1
- package/dist/migrate/export/sso-metadata.d.ts +0 -47
- package/dist/migrate/export/sso-metadata.js +0 -180
- package/dist/migrate/export/sso-metadata.js.map +0 -1
- package/dist/migrate/export/stytch-config.d.ts +0 -22
- package/dist/migrate/export/stytch-config.js +0 -102
- package/dist/migrate/export/stytch-config.js.map +0 -1
- package/dist/migrate/export/stytch.d.ts +0 -68
- package/dist/migrate/export/stytch.js +0 -510
- package/dist/migrate/export/stytch.js.map +0 -1
- package/dist/migrate/export/supabase-config.d.ts +0 -48
- package/dist/migrate/export/supabase-config.js +0 -408
- package/dist/migrate/export/supabase-config.js.map +0 -1
- package/dist/migrate/export/supabase.d.ts +0 -86
- package/dist/migrate/export/supabase.js +0 -524
- package/dist/migrate/export/supabase.js.map +0 -1
- package/dist/migrate/export/supertokens-config.d.ts +0 -20
- package/dist/migrate/export/supertokens-config.js +0 -150
- package/dist/migrate/export/supertokens-config.js.map +0 -1
- package/dist/migrate/export/supertokens.d.ts +0 -125
- package/dist/migrate/export/supertokens.js +0 -464
- package/dist/migrate/export/supertokens.js.map +0 -1
- package/dist/migrate/export/table.d.ts +0 -35
- package/dist/migrate/export/table.js +0 -248
- package/dist/migrate/export/table.js.map +0 -1
- package/dist/migrate/export/translate-kit.d.ts +0 -46
- package/dist/migrate/export/translate-kit.js +0 -148
- package/dist/migrate/export/translate-kit.js.map +0 -1
- package/dist/migrate/export/userfront-config.d.ts +0 -15
- package/dist/migrate/export/userfront-config.js +0 -49
- package/dist/migrate/export/userfront-config.js.map +0 -1
- package/dist/migrate/export/userfront.d.ts +0 -38
- package/dist/migrate/export/userfront.js +0 -189
- package/dist/migrate/export/userfront.js.map +0 -1
- package/dist/migrate/export/vendor-http.d.ts +0 -46
- package/dist/migrate/export/vendor-http.js +0 -138
- package/dist/migrate/export/vendor-http.js.map +0 -1
- package/dist/migrate/export/workos-config.d.ts +0 -23
- package/dist/migrate/export/workos-config.js +0 -125
- package/dist/migrate/export/workos-config.js.map +0 -1
- package/dist/migrate/export/workos.d.ts +0 -46
- package/dist/migrate/export/workos.js +0 -272
- package/dist/migrate/export/workos.js.map +0 -1
- package/dist/migrate/export/zip.d.ts +0 -8
- package/dist/migrate/export/zip.js +0 -66
- package/dist/migrate/export/zip.js.map +0 -1
- package/dist/migrate/export/zitadel-config.d.ts +0 -37
- package/dist/migrate/export/zitadel-config.js +0 -279
- package/dist/migrate/export/zitadel-config.js.map +0 -1
- package/dist/migrate/export/zitadel.d.ts +0 -82
- package/dist/migrate/export/zitadel.js +0 -447
- package/dist/migrate/export/zitadel.js.map +0 -1
- package/dist/migrate/firebase/plugin.d.ts +0 -2
- package/dist/migrate/firebase/plugin.js +0 -152
- package/dist/migrate/firebase/plugin.js.map +0 -1
- package/dist/migrate/framework/builtins.d.ts +0 -2
- package/dist/migrate/framework/builtins.js +0 -52
- package/dist/migrate/framework/builtins.js.map +0 -1
- package/dist/migrate/framework/commands.d.ts +0 -20
- package/dist/migrate/framework/commands.js +0 -58
- package/dist/migrate/framework/commands.js.map +0 -1
- package/dist/migrate/framework/custom.d.ts +0 -12
- package/dist/migrate/framework/custom.js +0 -96
- package/dist/migrate/framework/custom.js.map +0 -1
- package/dist/migrate/framework/orchestrator.d.ts +0 -113
- package/dist/migrate/framework/orchestrator.js +0 -532
- package/dist/migrate/framework/orchestrator.js.map +0 -1
- package/dist/migrate/framework/plugin.d.ts +0 -167
- package/dist/migrate/framework/plugin.js +0 -3
- package/dist/migrate/framework/plugin.js.map +0 -1
- package/dist/migrate/framework/registry.d.ts +0 -11
- package/dist/migrate/framework/registry.js +0 -44
- package/dist/migrate/framework/registry.js.map +0 -1
- package/dist/migrate/framework/report.d.ts +0 -17
- package/dist/migrate/framework/report.js +0 -103
- package/dist/migrate/framework/report.js.map +0 -1
- package/dist/migrate/frameworks/plugins.d.ts +0 -29
- package/dist/migrate/frameworks/plugins.js +0 -402
- package/dist/migrate/frameworks/plugins.js.map +0 -1
- package/dist/migrate/frameworks/report.d.ts +0 -92
- package/dist/migrate/frameworks/report.js +0 -207
- package/dist/migrate/frameworks/report.js.map +0 -1
- package/dist/migrate/frameworks/translate-js.d.ts +0 -5
- package/dist/migrate/frameworks/translate-js.js +0 -503
- package/dist/migrate/frameworks/translate-js.js.map +0 -1
- package/dist/migrate/frameworks/translate-rails-django.d.ts +0 -3
- package/dist/migrate/frameworks/translate-rails-django.js +0 -1391
- package/dist/migrate/frameworks/translate-rails-django.js.map +0 -1
- package/dist/migrate/frontegg/plugin.d.ts +0 -2
- package/dist/migrate/frontegg/plugin.js +0 -78
- package/dist/migrate/frontegg/plugin.js.map +0 -1
- package/dist/migrate/fusionauth/plugin.d.ts +0 -2
- package/dist/migrate/fusionauth/plugin.js +0 -109
- package/dist/migrate/fusionauth/plugin.js.map +0 -1
- package/dist/migrate/hanko/plugin.d.ts +0 -2
- package/dist/migrate/hanko/plugin.js +0 -100
- package/dist/migrate/hanko/plugin.js.map +0 -1
- package/dist/migrate/keycloak/plugin.d.ts +0 -2
- package/dist/migrate/keycloak/plugin.js +0 -148
- package/dist/migrate/keycloak/plugin.js.map +0 -1
- package/dist/migrate/kinde/plugin.d.ts +0 -2
- package/dist/migrate/kinde/plugin.js +0 -105
- package/dist/migrate/kinde/plugin.js.map +0 -1
- package/dist/migrate/ldap/plugin.d.ts +0 -2
- package/dist/migrate/ldap/plugin.js +0 -73
- package/dist/migrate/ldap/plugin.js.map +0 -1
- package/dist/migrate/logto/plugin.d.ts +0 -2
- package/dist/migrate/logto/plugin.js +0 -131
- package/dist/migrate/logto/plugin.js.map +0 -1
- package/dist/migrate/magic/plugin.d.ts +0 -2
- package/dist/migrate/magic/plugin.js +0 -56
- package/dist/migrate/magic/plugin.js.map +0 -1
- package/dist/migrate/nhost/plugin.d.ts +0 -2
- package/dist/migrate/nhost/plugin.js +0 -115
- package/dist/migrate/nhost/plugin.js.map +0 -1
- package/dist/migrate/oidc/plugin.d.ts +0 -2
- package/dist/migrate/oidc/plugin.js +0 -73
- package/dist/migrate/oidc/plugin.js.map +0 -1
- package/dist/migrate/okta/plugin.d.ts +0 -2
- package/dist/migrate/okta/plugin.js +0 -105
- package/dist/migrate/okta/plugin.js.map +0 -1
- package/dist/migrate/onelogin/plugin.d.ts +0 -2
- package/dist/migrate/onelogin/plugin.js +0 -61
- package/dist/migrate/onelogin/plugin.js.map +0 -1
- package/dist/migrate/ory/plugin.d.ts +0 -2
- package/dist/migrate/ory/plugin.js +0 -186
- package/dist/migrate/ory/plugin.js.map +0 -1
- package/dist/migrate/ping-identity/plugin.d.ts +0 -2
- package/dist/migrate/ping-identity/plugin.js +0 -100
- package/dist/migrate/ping-identity/plugin.js.map +0 -1
- package/dist/migrate/pocketbase/plugin.d.ts +0 -2
- package/dist/migrate/pocketbase/plugin.js +0 -99
- package/dist/migrate/pocketbase/plugin.js.map +0 -1
- package/dist/migrate/propelauth/plugin.d.ts +0 -2
- package/dist/migrate/propelauth/plugin.js +0 -65
- package/dist/migrate/propelauth/plugin.js.map +0 -1
- package/dist/migrate/saml/plugin.d.ts +0 -2
- package/dist/migrate/saml/plugin.js +0 -79
- package/dist/migrate/saml/plugin.js.map +0 -1
- package/dist/migrate/stytch/plugin.d.ts +0 -2
- package/dist/migrate/stytch/plugin.js +0 -97
- package/dist/migrate/stytch/plugin.js.map +0 -1
- package/dist/migrate/supabase/plugin.d.ts +0 -2
- package/dist/migrate/supabase/plugin.js +0 -149
- package/dist/migrate/supabase/plugin.js.map +0 -1
- package/dist/migrate/supertokens/plugin.d.ts +0 -2
- package/dist/migrate/supertokens/plugin.js +0 -114
- package/dist/migrate/supertokens/plugin.js.map +0 -1
- package/dist/migrate/user-table/plugin.d.ts +0 -2
- package/dist/migrate/user-table/plugin.js +0 -62
- package/dist/migrate/user-table/plugin.js.map +0 -1
- package/dist/migrate/userfront/plugin.d.ts +0 -2
- package/dist/migrate/userfront/plugin.js +0 -60
- package/dist/migrate/userfront/plugin.js.map +0 -1
- package/dist/migrate/workos/plugin.d.ts +0 -2
- package/dist/migrate/workos/plugin.js +0 -81
- package/dist/migrate/workos/plugin.js.map +0 -1
- package/dist/migrate/zitadel/plugin.d.ts +0 -2
- package/dist/migrate/zitadel/plugin.js +0 -114
- package/dist/migrate/zitadel/plugin.js.map +0 -1
- package/dist/output.d.ts +0 -41
- package/dist/output.js +0 -72
- package/dist/output.js.map +0 -1
- package/dist/project.d.ts +0 -64
- package/dist/project.js +0 -86
- package/dist/project.js.map +0 -1
- package/dist/prompt.d.ts +0 -15
- package/dist/prompt.js +0 -46
- package/dist/prompt.js.map +0 -1
- package/dist/run.d.ts +0 -12
- package/dist/run.js +0 -157
- package/dist/run.js.map +0 -1
- package/dist/secrets.d.ts +0 -41
- package/dist/secrets.js +0 -142
- package/dist/secrets.js.map +0 -1
- package/dist/serialize.d.ts +0 -30
- package/dist/serialize.js +0 -131
- package/dist/serialize.js.map +0 -1
- package/dist/version.d.ts +0 -7
- package/dist/version.js +0 -21
- package/dist/version.js.map +0 -1
package/dist/commands/config.js
DELETED
|
@@ -1,1430 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.config = config;
|
|
4
|
-
exports.resolveFormat = resolveFormat;
|
|
5
|
-
exports.planConfigImport = planConfigImport;
|
|
6
|
-
exports.diffFields = diffFields;
|
|
7
|
-
exports.readSecretsEnv = readSecretsEnv;
|
|
8
|
-
exports.unresolvedPlaceholders = unresolvedPlaceholders;
|
|
9
|
-
exports.configTypes = configTypes;
|
|
10
|
-
exports.planCommand = planCommand;
|
|
11
|
-
exports.applyCommand = applyCommand;
|
|
12
|
-
exports.printPlan = printPlan;
|
|
13
|
-
exports.interpolate = interpolate;
|
|
14
|
-
const node_fs_1 = require("node:fs");
|
|
15
|
-
const node_os_1 = require("node:os");
|
|
16
|
-
const node_path_1 = require("node:path");
|
|
17
|
-
const args_1 = require("../args");
|
|
18
|
-
const config_http_1 = require("../config-http");
|
|
19
|
-
const errors_1 = require("../errors");
|
|
20
|
-
const serialize_1 = require("../serialize");
|
|
21
|
-
const backend_1 = require("@atlasauth/backend");
|
|
22
|
-
const shared_1 = require("./shared");
|
|
23
|
-
const secrets_1 = require("../secrets");
|
|
24
|
-
// ───────────────────────────── small helpers ─────────────────────────────
|
|
25
|
-
/** GET a `{ data: [...] }` list envelope, tolerating an absent/garbled body. */
|
|
26
|
-
async function listData(http, path) {
|
|
27
|
-
const res = await http.get(path);
|
|
28
|
-
return Array.isArray(res?.data) ? res.data : [];
|
|
29
|
-
}
|
|
30
|
-
/** Build a config object from a row, keeping only the named keys and dropping null/undefined. */
|
|
31
|
-
function pick(row, keys) {
|
|
32
|
-
const out = {};
|
|
33
|
-
for (const k of keys) {
|
|
34
|
-
const v = row[k];
|
|
35
|
-
if (v === null || v === undefined)
|
|
36
|
-
continue;
|
|
37
|
-
out[k] = v;
|
|
38
|
-
}
|
|
39
|
-
return out;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* The write body: strip null/undefined. On a PATCH (`keepNull`) an explicit file
|
|
43
|
-
* `null` is kept so it clears a nullable field; an absent key stays unmanaged.
|
|
44
|
-
*/
|
|
45
|
-
function compact(config, keepNull = false) {
|
|
46
|
-
const out = {};
|
|
47
|
-
if (config && typeof config === 'object' && !Array.isArray(config)) {
|
|
48
|
-
for (const [k, v] of Object.entries(config)) {
|
|
49
|
-
if (v === undefined || (v === null && !keepNull))
|
|
50
|
-
continue;
|
|
51
|
-
out[k] = v;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return out;
|
|
55
|
-
}
|
|
56
|
-
function enc(segment) {
|
|
57
|
-
return encodeURIComponent(segment);
|
|
58
|
-
}
|
|
59
|
-
// ───────────────────────────── the registry ─────────────────────────────
|
|
60
|
-
/**
|
|
61
|
-
* The single source of truth for what `atlas config` round-trips — the same
|
|
62
|
-
* resource set the Terraform provider manages. Each entry maps a resource type
|
|
63
|
-
* to its list/create/update/delete endpoints and its redacted projection.
|
|
64
|
-
* SECRETS ARE NEVER PART OF A PROJECTION: the management API never returns them
|
|
65
|
-
* on a read, so they never reach disk; a credential that must be (re)written is
|
|
66
|
-
* supplied at import time via a `${ENV_VAR}` placeholder in the file.
|
|
67
|
-
*/
|
|
68
|
-
function registry() {
|
|
69
|
-
const specs = [
|
|
70
|
-
// 0. Email provider — first, because email sign-in strategies need one. Secrets
|
|
71
|
-
// (api_key, …) are write-only: the live side shows "(secret set)", never a value.
|
|
72
|
-
{
|
|
73
|
-
kind: 'singleton',
|
|
74
|
-
type: 'email_provider',
|
|
75
|
-
async fetchLive(http) {
|
|
76
|
-
let p;
|
|
77
|
-
try {
|
|
78
|
-
p = await http.get('/v1/messaging_providers/email');
|
|
79
|
-
}
|
|
80
|
-
catch (err) {
|
|
81
|
-
if ((0, config_http_1.isNotFound)(err))
|
|
82
|
-
return null;
|
|
83
|
-
throw err;
|
|
84
|
-
}
|
|
85
|
-
if (!p || p.configured === false)
|
|
86
|
-
return null;
|
|
87
|
-
const config = { ...obj((p.config ?? null)) };
|
|
88
|
-
const set = obj((p.secrets_set ?? null));
|
|
89
|
-
const names = Array.isArray(p.secrets_set) ? p.secrets_set : Object.keys(set).filter((k) => set[k] === true);
|
|
90
|
-
for (const k of names)
|
|
91
|
-
config[k] = SECRET_SET;
|
|
92
|
-
return pick({ ...p, config }, ['transport', 'from_address', 'config']);
|
|
93
|
-
},
|
|
94
|
-
async apply(http, config) {
|
|
95
|
-
await http.put('/v1/messaging_providers/email', compact(withoutMarkers(config)));
|
|
96
|
-
},
|
|
97
|
-
normalize: (c) => redactSecrets(c, ['config']),
|
|
98
|
-
},
|
|
99
|
-
// 1. Instance config (auth_config + allowed origins) — the §9.3 singleton.
|
|
100
|
-
{
|
|
101
|
-
kind: 'singleton',
|
|
102
|
-
type: 'instance',
|
|
103
|
-
async fetchLive(http) {
|
|
104
|
-
const inst = await http.get('/v1/instance');
|
|
105
|
-
const hints = obj(inst.allowed_origin_integrations ?? null);
|
|
106
|
-
const origins = originStrings(inst.allowed_origins ?? []).map((o) => (hints[o] ? { origin: o, integration: hints[o] } : o));
|
|
107
|
-
return { allowed_origins: origins, auth_config: stripOriginIntegrations(inst.auth_config ?? {}) };
|
|
108
|
-
},
|
|
109
|
-
async apply(http, config) {
|
|
110
|
-
const c = compact(config);
|
|
111
|
-
// Integration hints live in auth_config.originIntegrations server-side and a
|
|
112
|
-
// write replaces auth_config, so carry the live hints over for any origin the
|
|
113
|
-
// file names as a plain string (the plan ignores hints the file does not set).
|
|
114
|
-
const live = await http.get('/v1/instance');
|
|
115
|
-
const liveHints = obj(live.allowed_origin_integrations ?? null);
|
|
116
|
-
const authConfig = stripOriginIntegrations(c.auth_config ?? {});
|
|
117
|
-
const body = { auth_config: authConfig };
|
|
118
|
-
if (c.allowed_origins !== undefined) {
|
|
119
|
-
// A file without allowed_origins leaves the live origins alone (a partial
|
|
120
|
-
// document, e.g. one produced by `atlas migrate better-auth config`).
|
|
121
|
-
body.allowed_origins = (Array.isArray(c.allowed_origins) ? c.allowed_origins : []).map((e) => typeof e === 'string' && liveHints[e] ? { origin: e, integration: liveHints[e] } : e);
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
const kept = obj(live.auth_config ?? null).originIntegrations;
|
|
125
|
-
if (kept !== undefined)
|
|
126
|
-
authConfig.originIntegrations = kept;
|
|
127
|
-
}
|
|
128
|
-
await http.patch('/v1/instance', body);
|
|
129
|
-
},
|
|
130
|
-
normalize(c) {
|
|
131
|
-
const d = obj(c);
|
|
132
|
-
const hints = {};
|
|
133
|
-
for (const e of Array.isArray(d.allowed_origins) ? d.allowed_origins : []) {
|
|
134
|
-
const o = obj(e);
|
|
135
|
-
if (o.integration)
|
|
136
|
-
hints[String(o.origin)] = o.integration;
|
|
137
|
-
}
|
|
138
|
-
const out = {};
|
|
139
|
-
if (d.allowed_origins !== undefined)
|
|
140
|
-
out.allowed_origins = originStrings(d.allowed_origins);
|
|
141
|
-
if (Object.keys(hints).length)
|
|
142
|
-
out.allowed_origin_integrations = hints;
|
|
143
|
-
if (d.auth_config !== undefined)
|
|
144
|
-
out.auth_config = stripOriginIntegrations(d.auth_config);
|
|
145
|
-
return out;
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
// 2. Org settings schema (P1-5) — a singleton JSON Schema; 404 when unset.
|
|
149
|
-
{
|
|
150
|
-
kind: 'singleton',
|
|
151
|
-
type: 'org_settings_schema',
|
|
152
|
-
async fetchLive(http) {
|
|
153
|
-
try {
|
|
154
|
-
const s = await http.get('/v1/organization_settings_schema');
|
|
155
|
-
return { schema: s.schema ?? {} };
|
|
156
|
-
}
|
|
157
|
-
catch (err) {
|
|
158
|
-
if ((0, config_http_1.isNotFound)(err))
|
|
159
|
-
return null;
|
|
160
|
-
throw err;
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
async apply(http, config) {
|
|
164
|
-
const c = compact(config);
|
|
165
|
-
await http.put('/v1/organization_settings_schema', { schema: c.schema ?? {} });
|
|
166
|
-
},
|
|
167
|
-
},
|
|
168
|
-
// 3. Redirect URL allowlist — key = the (normalised) url; immutable, so no update.
|
|
169
|
-
{
|
|
170
|
-
kind: 'collection',
|
|
171
|
-
type: 'redirect_urls',
|
|
172
|
-
keyOf: (c) => String(obj(c).url ?? ''),
|
|
173
|
-
async fetchLive(http) {
|
|
174
|
-
const rows = await listData(http, '/v1/redirect_urls');
|
|
175
|
-
return rows.map((r) => ({ key: r.url, id: r.id, config: pick(r, ['url']) }));
|
|
176
|
-
},
|
|
177
|
-
async create(http, config) {
|
|
178
|
-
await http.post('/v1/redirect_urls', { url: obj(config).url });
|
|
179
|
-
},
|
|
180
|
-
async remove(http, live) {
|
|
181
|
-
await http.delete(`/v1/redirect_urls/${enc(live.id)}`);
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
// 4. OAuth clients (first-party / M2M apps) — key = name.
|
|
185
|
-
{
|
|
186
|
-
kind: 'collection',
|
|
187
|
-
type: 'oauth_clients',
|
|
188
|
-
keyOf: (c) => String(obj(c).name ?? ''),
|
|
189
|
-
async fetchLive(http) {
|
|
190
|
-
const rows = await listData(http, '/v1/oauth_clients');
|
|
191
|
-
return rows.map((r) => ({
|
|
192
|
-
key: String(r.name ?? ''),
|
|
193
|
-
id: String(r.id ?? ''),
|
|
194
|
-
config: pick(r, OAUTH_CLIENT_KEYS),
|
|
195
|
-
}));
|
|
196
|
-
},
|
|
197
|
-
async create(http, config) {
|
|
198
|
-
await http.post('/v1/oauth_clients', compact(config));
|
|
199
|
-
},
|
|
200
|
-
async update(http, live, config) {
|
|
201
|
-
await http.patch(`/v1/oauth_clients/${enc(live.id)}`, compact(config, true));
|
|
202
|
-
},
|
|
203
|
-
async remove(http, live) {
|
|
204
|
-
await http.delete(`/v1/oauth_clients/${enc(live.id)}`);
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
// 4b. Hooks — after the clients. before_user_create: url, timeout_ms, fail_closed,
|
|
208
|
-
// enabled; the signing secret is write-only ("(secret set)").
|
|
209
|
-
{
|
|
210
|
-
kind: 'singleton',
|
|
211
|
-
type: 'hooks',
|
|
212
|
-
async fetchLive(http) {
|
|
213
|
-
let h;
|
|
214
|
-
try {
|
|
215
|
-
h = await http.get('/v1/instance/hooks/before_user_create');
|
|
216
|
-
}
|
|
217
|
-
catch (err) {
|
|
218
|
-
if ((0, config_http_1.isNotFound)(err))
|
|
219
|
-
return null;
|
|
220
|
-
throw err;
|
|
221
|
-
}
|
|
222
|
-
if (!h || (h.url == null && h.enabled !== true))
|
|
223
|
-
return null;
|
|
224
|
-
const bu = pick(h, ['url', 'timeout_ms', 'fail_closed', 'enabled']);
|
|
225
|
-
if (h.signing_secret_set === true)
|
|
226
|
-
bu.signing_secret = SECRET_SET;
|
|
227
|
-
return { before_user_create: bu };
|
|
228
|
-
},
|
|
229
|
-
async apply(http, config, opts) {
|
|
230
|
-
const bu = compact(obj(withoutMarkers(config)).before_user_create ?? {}, true);
|
|
231
|
-
if (Object.keys(bu).length === 0)
|
|
232
|
-
return;
|
|
233
|
-
try {
|
|
234
|
-
await http.patch(`/v1/instance/hooks/before_user_create${opts.forceHook ? '?force=true' : ''}`, bu);
|
|
235
|
-
}
|
|
236
|
-
catch (err) {
|
|
237
|
-
const refused = err instanceof backend_1.AtlasApiError && err.status === 409 ? err.errors.find((e) => e.code === 'HOOK_TEST_FAILED') : undefined;
|
|
238
|
-
if (refused) {
|
|
239
|
-
throw new errors_1.CliError(`hooks.before_user_create: the change was refused because the hook test call failed (HOOK_TEST_FAILED): ${refused.message} ` +
|
|
240
|
-
'The hook was left as it was. Deploy the route, or pass --force-hook to apply it anyway.');
|
|
241
|
-
}
|
|
242
|
-
throw err;
|
|
243
|
-
}
|
|
244
|
-
},
|
|
245
|
-
normalize: (c) => redactSecrets(c, ['before_user_create']),
|
|
246
|
-
},
|
|
247
|
-
// 5. Social / enterprise OAuth providers — key = the catalog provider slug.
|
|
248
|
-
{
|
|
249
|
-
kind: 'collection',
|
|
250
|
-
type: 'oauth_providers',
|
|
251
|
-
keyOf: (c) => String(obj(c).provider ?? ''),
|
|
252
|
-
async fetchLive(http) {
|
|
253
|
-
const rows = await listData(http, '/v1/oauth_providers');
|
|
254
|
-
return rows
|
|
255
|
-
.filter((r) => r.configured === true)
|
|
256
|
-
.map((r) => ({
|
|
257
|
-
key: String(r.provider ?? ''),
|
|
258
|
-
id: String(r.provider ?? ''),
|
|
259
|
-
config: pick(r, OAUTH_PROVIDER_KEYS),
|
|
260
|
-
}));
|
|
261
|
-
},
|
|
262
|
-
create: putProvider,
|
|
263
|
-
update: (http, _live, config) => putProvider(http, config),
|
|
264
|
-
async remove(http, live) {
|
|
265
|
-
await http.delete(`/v1/oauth_providers/${enc(live.id)}`);
|
|
266
|
-
},
|
|
267
|
-
},
|
|
268
|
-
// 5b. Custom (generic) OAuth 2.0 / OIDC login providers — key = provider_key.
|
|
269
|
-
{
|
|
270
|
-
kind: 'collection',
|
|
271
|
-
type: 'custom_oauth_providers',
|
|
272
|
-
keyOf: (c) => String(obj(c).provider_key ?? ''),
|
|
273
|
-
async fetchLive(http) {
|
|
274
|
-
const rows = await listData(http, '/v1/custom_oauth_providers');
|
|
275
|
-
return rows.map((r) => ({
|
|
276
|
-
key: String(r.provider_key ?? ''),
|
|
277
|
-
id: String(r.id ?? ''),
|
|
278
|
-
config: pick(r, CUSTOM_PROVIDER_KEYS),
|
|
279
|
-
}));
|
|
280
|
-
},
|
|
281
|
-
async create(http, config) {
|
|
282
|
-
await http.post('/v1/custom_oauth_providers', compact(config));
|
|
283
|
-
},
|
|
284
|
-
async update(http, live, config) {
|
|
285
|
-
const body = compact(config, true);
|
|
286
|
-
delete body.provider_key; // immutable
|
|
287
|
-
await http.patch(`/v1/custom_oauth_providers/${enc(live.id)}`, body);
|
|
288
|
-
},
|
|
289
|
-
async remove(http, live) {
|
|
290
|
-
await http.delete(`/v1/custom_oauth_providers/${enc(live.id)}`);
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
// 6. Resource servers (API audiences) — key = identifier.
|
|
294
|
-
{
|
|
295
|
-
kind: 'collection',
|
|
296
|
-
type: 'resource_servers',
|
|
297
|
-
keyOf: (c) => String(obj(c).identifier ?? ''),
|
|
298
|
-
async fetchLive(http) {
|
|
299
|
-
const rows = await listData(http, '/v1/resource_servers');
|
|
300
|
-
return rows.map((r) => ({
|
|
301
|
-
key: String(r.identifier ?? ''),
|
|
302
|
-
id: String(r.id ?? ''),
|
|
303
|
-
config: pick(r, RESOURCE_SERVER_KEYS),
|
|
304
|
-
}));
|
|
305
|
-
},
|
|
306
|
-
async create(http, config) {
|
|
307
|
-
const body = compact(config);
|
|
308
|
-
delete body.signing_alg; // read-only (reported, not settable)
|
|
309
|
-
await http.post('/v1/resource_servers', body);
|
|
310
|
-
},
|
|
311
|
-
async update(http, live, config) {
|
|
312
|
-
const body = compact(config, true);
|
|
313
|
-
delete body.identifier; // immutable
|
|
314
|
-
delete body.signing_alg; // read-only
|
|
315
|
-
await http.patch(`/v1/resource_servers/${enc(live.id)}`, body);
|
|
316
|
-
},
|
|
317
|
-
async remove(http, live) {
|
|
318
|
-
await http.delete(`/v1/resource_servers/${enc(live.id)}`);
|
|
319
|
-
},
|
|
320
|
-
},
|
|
321
|
-
// 7. JWT templates — key = name; claims is a flat template map.
|
|
322
|
-
{
|
|
323
|
-
kind: 'collection',
|
|
324
|
-
type: 'jwt_templates',
|
|
325
|
-
keyOf: (c) => String(obj(c).name ?? ''),
|
|
326
|
-
async fetchLive(http) {
|
|
327
|
-
const rows = await listData(http, '/v1/jwt_templates');
|
|
328
|
-
return rows.map((r) => ({
|
|
329
|
-
key: r.name,
|
|
330
|
-
id: r.name,
|
|
331
|
-
config: { name: r.name, claims: r.claims ?? {} },
|
|
332
|
-
}));
|
|
333
|
-
},
|
|
334
|
-
async create(http, config) {
|
|
335
|
-
const c = obj(config);
|
|
336
|
-
await http.post('/v1/jwt_templates', { name: c.name, claims: c.claims ?? {} });
|
|
337
|
-
},
|
|
338
|
-
async update(http, live, config) {
|
|
339
|
-
await http.patch(`/v1/jwt_templates/${enc(live.id)}`, { claims: obj(config).claims ?? {} });
|
|
340
|
-
},
|
|
341
|
-
async remove(http, live) {
|
|
342
|
-
await http.delete(`/v1/jwt_templates/${enc(live.id)}`);
|
|
343
|
-
},
|
|
344
|
-
},
|
|
345
|
-
// 7b. Custom permissions — key = key. Before roles, which grant them. System ones skipped.
|
|
346
|
-
{
|
|
347
|
-
kind: 'collection',
|
|
348
|
-
type: 'permissions',
|
|
349
|
-
keyOf: (c) => String(obj(c).key ?? ''),
|
|
350
|
-
async fetchLive(http) {
|
|
351
|
-
const rows = await listData(http, '/v1/permissions');
|
|
352
|
-
return rows
|
|
353
|
-
.filter((r) => r.is_system !== true)
|
|
354
|
-
.map((r) => ({ key: String(r.key ?? ''), id: String(r.id ?? ''), config: pick(r, PERMISSION_KEYS) }));
|
|
355
|
-
},
|
|
356
|
-
async create(http, config) {
|
|
357
|
-
await http.post('/v1/permissions', compact(config));
|
|
358
|
-
},
|
|
359
|
-
async update(http, live, config) {
|
|
360
|
-
const c = obj(config);
|
|
361
|
-
await http.patch(`/v1/permissions/${enc(live.id)}`, { name: c.name, description: c.description ?? null });
|
|
362
|
-
},
|
|
363
|
-
async remove(http, live) {
|
|
364
|
-
await http.delete(`/v1/permissions/${enc(live.id)}`);
|
|
365
|
-
},
|
|
366
|
-
},
|
|
367
|
-
// 8. Roles — key = key; label via PATCH, permission set via PUT. System roles skipped.
|
|
368
|
-
{
|
|
369
|
-
kind: 'collection',
|
|
370
|
-
type: 'roles',
|
|
371
|
-
keyOf: (c) => String(obj(c).key ?? ''),
|
|
372
|
-
async fetchLive(http) {
|
|
373
|
-
const rows = await listData(http, '/v1/roles');
|
|
374
|
-
return rows
|
|
375
|
-
.filter((r) => r.is_system !== true)
|
|
376
|
-
.map((r) => ({
|
|
377
|
-
key: String(r.key ?? ''),
|
|
378
|
-
id: String(r.id ?? ''),
|
|
379
|
-
config: pick(r, ROLE_KEYS),
|
|
380
|
-
}));
|
|
381
|
-
},
|
|
382
|
-
async create(http, config) {
|
|
383
|
-
await http.post('/v1/roles', compact(config));
|
|
384
|
-
},
|
|
385
|
-
async update(http, live, config) {
|
|
386
|
-
const c = obj(config);
|
|
387
|
-
await http.patch(`/v1/roles/${enc(live.id)}`, {
|
|
388
|
-
name: c.name,
|
|
389
|
-
description: c.description ?? null,
|
|
390
|
-
});
|
|
391
|
-
await http.put(`/v1/roles/${enc(live.id)}/permissions`, {
|
|
392
|
-
permissions: Array.isArray(c.permissions) ? c.permissions : [],
|
|
393
|
-
});
|
|
394
|
-
},
|
|
395
|
-
async remove(http, live) {
|
|
396
|
-
await http.delete(`/v1/roles/${enc(live.id)}`);
|
|
397
|
-
},
|
|
398
|
-
},
|
|
399
|
-
// 9. Webhook endpoints — key = url.
|
|
400
|
-
{
|
|
401
|
-
kind: 'collection',
|
|
402
|
-
type: 'webhook_endpoints',
|
|
403
|
-
keyOf: (c) => String(obj(c).url ?? ''),
|
|
404
|
-
async fetchLive(http) {
|
|
405
|
-
const rows = await listData(http, '/v1/webhook_endpoints');
|
|
406
|
-
return rows.map((r) => ({
|
|
407
|
-
key: String(r.url ?? ''),
|
|
408
|
-
id: String(r.id ?? ''),
|
|
409
|
-
config: pick(r, WEBHOOK_KEYS),
|
|
410
|
-
...(typeof r.managed_by === 'string' && r.managed_by ? { managedBy: r.managed_by } : {}),
|
|
411
|
-
}));
|
|
412
|
-
},
|
|
413
|
-
async create(http, config) {
|
|
414
|
-
const c = obj(config);
|
|
415
|
-
await http.post('/v1/webhook_endpoints', {
|
|
416
|
-
url: c.url,
|
|
417
|
-
enabled_events: c.enabled_events ?? [],
|
|
418
|
-
// Sent on create so a paused endpoint is never live, even briefly.
|
|
419
|
-
...(typeof c.active === 'boolean' ? { active: c.active } : {}),
|
|
420
|
-
});
|
|
421
|
-
},
|
|
422
|
-
async update(http, live, config) {
|
|
423
|
-
await http.patch(`/v1/webhook_endpoints/${enc(live.id)}`, compact(config, true));
|
|
424
|
-
},
|
|
425
|
-
async remove(http, live) {
|
|
426
|
-
await http.delete(`/v1/webhook_endpoints/${enc(live.id)}`);
|
|
427
|
-
},
|
|
428
|
-
},
|
|
429
|
-
// 10. SSO connections — key = a stable identifier derived from the connection.
|
|
430
|
-
{
|
|
431
|
-
kind: 'collection',
|
|
432
|
-
type: 'sso_connections',
|
|
433
|
-
keyOf: ssoKey,
|
|
434
|
-
async fetchLive(http) {
|
|
435
|
-
const rows = await listData(http, '/v1/sso_connections');
|
|
436
|
-
return rows.map((r) => {
|
|
437
|
-
const config = pick(r, SSO_KEYS);
|
|
438
|
-
return { key: ssoKey(config), id: String(r.id ?? ''), config };
|
|
439
|
-
});
|
|
440
|
-
},
|
|
441
|
-
async create(http, config) {
|
|
442
|
-
await http.post('/v1/sso_connections', compact(config));
|
|
443
|
-
},
|
|
444
|
-
async update(http, live, config) {
|
|
445
|
-
const body = compact(config, true);
|
|
446
|
-
delete body.type; // fixed at creation; PATCH does not take it
|
|
447
|
-
await http.patch(`/v1/sso_connections/${enc(live.id)}`, body);
|
|
448
|
-
},
|
|
449
|
-
async remove(http, live) {
|
|
450
|
-
await http.delete(`/v1/sso_connections/${enc(live.id)}`);
|
|
451
|
-
},
|
|
452
|
-
},
|
|
453
|
-
// 10b. SSO onboarding profiles (hosted setup links) — key = name. After the SSO
|
|
454
|
-
// connections; a profile may name an organization that already exists.
|
|
455
|
-
{
|
|
456
|
-
kind: 'collection',
|
|
457
|
-
type: 'sso_onboarding_profiles',
|
|
458
|
-
keyOf: (c) => String(obj(c).name ?? ''),
|
|
459
|
-
async fetchLive(http) {
|
|
460
|
-
let rows;
|
|
461
|
-
try {
|
|
462
|
-
rows = await listData(http, '/v1/sso_onboarding_profiles');
|
|
463
|
-
}
|
|
464
|
-
catch (err) {
|
|
465
|
-
if ((0, config_http_1.isNotFound)(err))
|
|
466
|
-
return [];
|
|
467
|
-
throw err;
|
|
468
|
-
}
|
|
469
|
-
return rows.map((r) => {
|
|
470
|
-
const config = pick(r, SSO_PROFILE_KEYS);
|
|
471
|
-
return { key: String(r.name ?? ''), id: String(r.id ?? ''), config };
|
|
472
|
-
});
|
|
473
|
-
},
|
|
474
|
-
async create(http, config) {
|
|
475
|
-
await http.post('/v1/sso_onboarding_profiles', compact(config));
|
|
476
|
-
},
|
|
477
|
-
async update(http, live, config) {
|
|
478
|
-
await http.patch(`/v1/sso_onboarding_profiles/${enc(live.id)}`, compact(config, true));
|
|
479
|
-
},
|
|
480
|
-
async remove(http, live) {
|
|
481
|
-
await http.delete(`/v1/sso_onboarding_profiles/${enc(live.id)}`);
|
|
482
|
-
},
|
|
483
|
-
},
|
|
484
|
-
// 11. Billing plans — key = slug.
|
|
485
|
-
{
|
|
486
|
-
kind: 'collection',
|
|
487
|
-
type: 'billing_plans',
|
|
488
|
-
keyOf: (c) => String(obj(c).slug ?? ''),
|
|
489
|
-
async fetchLive(http) {
|
|
490
|
-
const rows = await listData(http, '/v1/billing/plans');
|
|
491
|
-
return rows.map((r) => ({
|
|
492
|
-
key: String(r.slug ?? ''),
|
|
493
|
-
id: String(r.id ?? ''),
|
|
494
|
-
config: pick(r, BILLING_KEYS),
|
|
495
|
-
}));
|
|
496
|
-
},
|
|
497
|
-
async create(http, config) {
|
|
498
|
-
await http.post('/v1/billing/plans', compact(config));
|
|
499
|
-
},
|
|
500
|
-
async update(http, live, config) {
|
|
501
|
-
const body = compact(config, true);
|
|
502
|
-
delete body.slug; // immutable
|
|
503
|
-
await http.patch(`/v1/billing/plans/${enc(live.id)}`, body);
|
|
504
|
-
},
|
|
505
|
-
async remove(http, live) {
|
|
506
|
-
await http.delete(`/v1/billing/plans/${enc(live.id)}`);
|
|
507
|
-
},
|
|
508
|
-
},
|
|
509
|
-
// 12. Notification templates (P2-12) — key = name; replace via PUT.
|
|
510
|
-
{
|
|
511
|
-
kind: 'collection',
|
|
512
|
-
type: 'notification_templates',
|
|
513
|
-
keyOf: (c) => String(obj(c).name ?? ''),
|
|
514
|
-
async fetchLive(http) {
|
|
515
|
-
const rows = await listData(http, '/v1/notification_templates');
|
|
516
|
-
return rows.map((r) => ({
|
|
517
|
-
key: String(r.name ?? ''),
|
|
518
|
-
id: String(r.name ?? ''),
|
|
519
|
-
config: pick(r, NOTIFICATION_KEYS),
|
|
520
|
-
}));
|
|
521
|
-
},
|
|
522
|
-
async create(http, config) {
|
|
523
|
-
await http.post('/v1/notification_templates', compact(config));
|
|
524
|
-
},
|
|
525
|
-
async update(http, live, config) {
|
|
526
|
-
const body = compact(config, true);
|
|
527
|
-
delete body.name; // the name is the URL key
|
|
528
|
-
await http.put(`/v1/notification_templates/${enc(live.id)}`, body);
|
|
529
|
-
},
|
|
530
|
-
async remove(http, live) {
|
|
531
|
-
await http.delete(`/v1/notification_templates/${enc(live.id)}`);
|
|
532
|
-
},
|
|
533
|
-
},
|
|
534
|
-
// 13. Email templates (§11.1) — key = name; only the CUSTOMISED overrides are config.
|
|
535
|
-
{
|
|
536
|
-
kind: 'collection',
|
|
537
|
-
type: 'email_templates',
|
|
538
|
-
keyOf: (c) => String(obj(c).name ?? ''),
|
|
539
|
-
async fetchLive(http) {
|
|
540
|
-
const rows = await listData(http, '/v1/email_templates');
|
|
541
|
-
return rows
|
|
542
|
-
.filter((r) => r.customised && r.override)
|
|
543
|
-
.map((r) => ({
|
|
544
|
-
key: r.name,
|
|
545
|
-
id: r.name,
|
|
546
|
-
config: pick({ name: r.name, ...(r.override ?? {}) }, EMAIL_KEYS),
|
|
547
|
-
}));
|
|
548
|
-
},
|
|
549
|
-
async create(http, config) {
|
|
550
|
-
await putEmail(http, config);
|
|
551
|
-
},
|
|
552
|
-
async update(http, _live, config) {
|
|
553
|
-
await putEmail(http, config);
|
|
554
|
-
},
|
|
555
|
-
async remove(http, live) {
|
|
556
|
-
await http.delete(`/v1/email_templates/${enc(live.id)}`);
|
|
557
|
-
},
|
|
558
|
-
},
|
|
559
|
-
// 14. Organization policies — key = organization_id; attaches to existing orgs.
|
|
560
|
-
{
|
|
561
|
-
kind: 'collection',
|
|
562
|
-
type: 'organization_policies',
|
|
563
|
-
keyOf: (c) => String(obj(c).organization_id ?? ''),
|
|
564
|
-
async fetchLive(http) {
|
|
565
|
-
const orgs = await listData(http, '/v1/organizations');
|
|
566
|
-
const out = [];
|
|
567
|
-
for (const org of orgs) {
|
|
568
|
-
// There is no GET for org policy; an empty PATCH returns it unchanged.
|
|
569
|
-
const res = await http.patch(`/v1/organizations/${enc(org.id)}/policy`, {});
|
|
570
|
-
const policy = res?.policy ?? {};
|
|
571
|
-
const config = pick({ organization_id: org.id, ...policy }, ORG_POLICY_KEYS);
|
|
572
|
-
// Only orgs with a real (non-default) policy are config.
|
|
573
|
-
if (Object.keys(obj(config)).length > 1) {
|
|
574
|
-
out.push({ key: org.id, id: org.id, config });
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
return out;
|
|
578
|
-
},
|
|
579
|
-
async create(http, config) {
|
|
580
|
-
await patchOrgPolicy(http, config);
|
|
581
|
-
},
|
|
582
|
-
async update(http, _live, config) {
|
|
583
|
-
await patchOrgPolicy(http, config);
|
|
584
|
-
},
|
|
585
|
-
// No delete: pruning a policy would silently loosen an org's security, so
|
|
586
|
-
// org policies are never deleted by import (reported as skipped).
|
|
587
|
-
},
|
|
588
|
-
// 12. Branding — the hosted-page appearance; a partial merge on write (PATCH).
|
|
589
|
-
{
|
|
590
|
-
kind: 'singleton',
|
|
591
|
-
type: 'branding',
|
|
592
|
-
async fetchLive(http) {
|
|
593
|
-
let b;
|
|
594
|
-
try {
|
|
595
|
-
b = await http.get('/v1/branding');
|
|
596
|
-
}
|
|
597
|
-
catch (err) {
|
|
598
|
-
if ((0, config_http_1.isNotFound)(err))
|
|
599
|
-
return null;
|
|
600
|
-
throw err;
|
|
601
|
-
}
|
|
602
|
-
const { object: _o, resolved: _r, ...fields } = b ?? {};
|
|
603
|
-
const out = compact(fields);
|
|
604
|
-
return Object.keys(out).length ? out : null;
|
|
605
|
-
},
|
|
606
|
-
async apply(http, config) {
|
|
607
|
-
await http.patch('/v1/branding', compact(config, true));
|
|
608
|
-
},
|
|
609
|
-
},
|
|
610
|
-
// 13. Cookie-consent banner — last: it only needs the instance. PUT replaces the
|
|
611
|
-
// whole setting and needs script_url, so apply merges the file over the live one.
|
|
612
|
-
{
|
|
613
|
-
kind: 'singleton',
|
|
614
|
-
type: 'cookie_consent',
|
|
615
|
-
async fetchLive(http) {
|
|
616
|
-
let cc;
|
|
617
|
-
try {
|
|
618
|
-
cc = await http.get('/v1/instance/cookie_consent');
|
|
619
|
-
}
|
|
620
|
-
catch (err) {
|
|
621
|
-
if ((0, config_http_1.isNotFound)(err))
|
|
622
|
-
return null;
|
|
623
|
-
throw err;
|
|
624
|
-
}
|
|
625
|
-
const out = pick(cc ?? {}, COOKIE_CONSENT_KEYS);
|
|
626
|
-
return Object.keys(out).length ? out : null;
|
|
627
|
-
},
|
|
628
|
-
async apply(http, config) {
|
|
629
|
-
const live = await http.get('/v1/instance/cookie_consent');
|
|
630
|
-
const body = { ...pick(live ?? {}, COOKIE_CONSENT_KEYS), ...compact(config, true) };
|
|
631
|
-
if (body.script_url === undefined || body.script_url === null) {
|
|
632
|
-
throw new errors_1.CliError('cookie_consent needs a script_url (the consent manager script) when none is set on the instance.');
|
|
633
|
-
}
|
|
634
|
-
await http.put('/v1/instance/cookie_consent', body);
|
|
635
|
-
},
|
|
636
|
-
},
|
|
637
|
-
// 14. "Sign in with Atlas" button on the hosted pages. The file says whether it
|
|
638
|
-
// is on; `connection_id` (optional) links an SSO connection the instance
|
|
639
|
-
// already has instead of the preset one. Only `enabled` is exported or
|
|
640
|
-
// compared: the connection id is this instance's own.
|
|
641
|
-
{
|
|
642
|
-
kind: 'singleton',
|
|
643
|
-
type: 'sign_in_with_atlas',
|
|
644
|
-
async fetchLive(http) {
|
|
645
|
-
let live;
|
|
646
|
-
try {
|
|
647
|
-
live = await http.get('/v1/instance/sign_in_with_atlas');
|
|
648
|
-
}
|
|
649
|
-
catch (err) {
|
|
650
|
-
if ((0, config_http_1.isNotFound)(err))
|
|
651
|
-
return null;
|
|
652
|
-
throw err;
|
|
653
|
-
}
|
|
654
|
-
return { enabled: live?.enabled === true };
|
|
655
|
-
},
|
|
656
|
-
normalize: (c) => ({ enabled: obj(c).enabled === true }),
|
|
657
|
-
async apply(http, config) {
|
|
658
|
-
const c = obj(config);
|
|
659
|
-
if (typeof c.enabled !== 'boolean')
|
|
660
|
-
throw new errors_1.CliError('sign_in_with_atlas needs enabled: true or false.');
|
|
661
|
-
await http.patch('/v1/instance/sign_in_with_atlas', {
|
|
662
|
-
enabled: c.enabled,
|
|
663
|
-
...(c.connection_id !== undefined ? { connection_id: c.connection_id } : {}),
|
|
664
|
-
});
|
|
665
|
-
},
|
|
666
|
-
},
|
|
667
|
-
];
|
|
668
|
-
return specs;
|
|
669
|
-
}
|
|
670
|
-
const OAUTH_CLIENT_KEYS = [
|
|
671
|
-
'name',
|
|
672
|
-
'redirect_uris',
|
|
673
|
-
'allowed_scopes',
|
|
674
|
-
'grant_types',
|
|
675
|
-
'token_endpoint_auth_method',
|
|
676
|
-
'logo_url',
|
|
677
|
-
'first_party',
|
|
678
|
-
'post_logout_redirect_uris',
|
|
679
|
-
'claims_redirect_uris',
|
|
680
|
-
'backchannel_logout_uri',
|
|
681
|
-
'backchannel_logout_session_required',
|
|
682
|
-
'frontchannel_logout_uri',
|
|
683
|
-
'frontchannel_logout_session_required',
|
|
684
|
-
'skip_logout_confirmation',
|
|
685
|
-
'refresh_touches_session',
|
|
686
|
-
'require_verified_email',
|
|
687
|
-
'require_pkce',
|
|
688
|
-
'login_url',
|
|
689
|
-
'consent_url',
|
|
690
|
-
'subject_type',
|
|
691
|
-
'sector_identifier_uri',
|
|
692
|
-
'access_token_format',
|
|
693
|
-
'token_lifetimes',
|
|
694
|
-
];
|
|
695
|
-
const OAUTH_PROVIDER_KEYS = [
|
|
696
|
-
'provider',
|
|
697
|
-
'enabled',
|
|
698
|
-
'allow_sign_in',
|
|
699
|
-
'allow_sign_up',
|
|
700
|
-
'scopes',
|
|
701
|
-
'client_id',
|
|
702
|
-
];
|
|
703
|
-
const RESOURCE_SERVER_KEYS = [
|
|
704
|
-
'identifier',
|
|
705
|
-
'name',
|
|
706
|
-
'scopes',
|
|
707
|
-
'token_ttl_seconds',
|
|
708
|
-
'signing_alg',
|
|
709
|
-
];
|
|
710
|
-
const ROLE_KEYS = ['key', 'name', 'description', 'permissions'];
|
|
711
|
-
const PERMISSION_KEYS = ['key', 'name', 'description'];
|
|
712
|
-
const CUSTOM_PROVIDER_KEYS = [
|
|
713
|
-
'provider_key',
|
|
714
|
-
'display_name',
|
|
715
|
-
'icon_url',
|
|
716
|
-
'protocol',
|
|
717
|
-
'issuer',
|
|
718
|
-
'discovery_url',
|
|
719
|
-
'authorization_endpoint',
|
|
720
|
-
'token_endpoint',
|
|
721
|
-
'userinfo_endpoint',
|
|
722
|
-
'jwks_uri',
|
|
723
|
-
'scopes',
|
|
724
|
-
'pkce',
|
|
725
|
-
'authorization_params',
|
|
726
|
-
'token_params',
|
|
727
|
-
'token_endpoint_auth_method',
|
|
728
|
-
'profile_source',
|
|
729
|
-
'profile_mapping',
|
|
730
|
-
'client_id',
|
|
731
|
-
'enabled',
|
|
732
|
-
'allow_sign_in',
|
|
733
|
-
'allow_sign_up',
|
|
734
|
-
];
|
|
735
|
-
const WEBHOOK_KEYS = ['url', 'enabled_events', 'active'];
|
|
736
|
-
const SSO_KEYS = [
|
|
737
|
-
'type',
|
|
738
|
-
'status',
|
|
739
|
-
'organization_id',
|
|
740
|
-
'oidc_issuer',
|
|
741
|
-
'oidc_client_id',
|
|
742
|
-
'saml_idp_entity_id',
|
|
743
|
-
'saml_idp_sso_url',
|
|
744
|
-
'saml_sp_entity_id',
|
|
745
|
-
'saml_allow_idp_initiated',
|
|
746
|
-
'saml_sign_authn_requests',
|
|
747
|
-
'saml_want_response_signed',
|
|
748
|
-
'discourse_provider_url',
|
|
749
|
-
'allowed_domains',
|
|
750
|
-
'claim_role_mappings',
|
|
751
|
-
'default_role_id',
|
|
752
|
-
];
|
|
753
|
-
const SSO_PROFILE_KEYS = ['name', 'allowed_connection_types', 'organization_id', 'company_name', 'allow_scim', 'intents', 'return_url'];
|
|
754
|
-
const COOKIE_CONSENT_KEYS = ['enabled', 'script_url', 'script_id', 'site_id', 'blocking_mode', 'extra_origins', 'settings_link', 'culture', 'host_aliases'];
|
|
755
|
-
const BILLING_KEYS = [
|
|
756
|
-
'name',
|
|
757
|
-
'slug',
|
|
758
|
-
'stripe_price_id',
|
|
759
|
-
'interval',
|
|
760
|
-
'amount',
|
|
761
|
-
'currency',
|
|
762
|
-
'features',
|
|
763
|
-
'limits',
|
|
764
|
-
'audience',
|
|
765
|
-
'active',
|
|
766
|
-
'pricing_model',
|
|
767
|
-
'trial_days',
|
|
768
|
-
'stripe_meter_id',
|
|
769
|
-
'usage_unit',
|
|
770
|
-
'provider',
|
|
771
|
-
'annual_price_id',
|
|
772
|
-
'kind',
|
|
773
|
-
];
|
|
774
|
-
const NOTIFICATION_KEYS = ['name', 'subject', 'body', 'category'];
|
|
775
|
-
const EMAIL_KEYS = ['name', 'subject', 'text'];
|
|
776
|
-
const ORG_POLICY_KEYS = [
|
|
777
|
-
'organization_id',
|
|
778
|
-
'requireMfa',
|
|
779
|
-
'ssoRequired',
|
|
780
|
-
'sessionIdleOverrideMs',
|
|
781
|
-
'allowedSignInMethods',
|
|
782
|
-
'ipAllowlist',
|
|
783
|
-
'maxSessionAgeSeconds',
|
|
784
|
-
];
|
|
785
|
-
/** Narrow a Config to a plain string-keyed record for field access. */
|
|
786
|
-
function obj(config) {
|
|
787
|
-
return config && typeof config === 'object' && !Array.isArray(config)
|
|
788
|
-
? config
|
|
789
|
-
: {};
|
|
790
|
-
}
|
|
791
|
-
/** What a write-only secret looks like on the live side, and in a plan. */
|
|
792
|
-
const SECRET_SET = '(secret set)';
|
|
793
|
-
const SECRET_KEY = /(secret|token|password|passwd|api_?key|private_?key)/i;
|
|
794
|
-
/**
|
|
795
|
-
* Show each secret value under `groups` as "(secret set)" so a plan never echoes it
|
|
796
|
-
* and compares equal to the live "(secret set)". An unresolved \${VAR} stays visible
|
|
797
|
-
* (apply refuses it), as does an empty value.
|
|
798
|
-
*/
|
|
799
|
-
function redactSecrets(config, groups) {
|
|
800
|
-
const out = { ...obj(config) };
|
|
801
|
-
const mask = (g) => Object.fromEntries(Object.entries(g).map(([k, v]) => [k, SECRET_KEY.test(k) && typeof v === 'string' && v !== '' && !/^\$\{[A-Z0-9_]+\}$/.test(v) ? SECRET_SET : v]));
|
|
802
|
-
for (const g of groups)
|
|
803
|
-
if (out[g] && typeof out[g] === 'object' && !Array.isArray(out[g]))
|
|
804
|
-
out[g] = mask(obj(out[g]));
|
|
805
|
-
return out;
|
|
806
|
-
}
|
|
807
|
-
/** A document without any "(secret set)" marker: what an export writes and what apply sends. */
|
|
808
|
-
function withoutMarkers(value) {
|
|
809
|
-
if (Array.isArray(value))
|
|
810
|
-
return value.map(withoutMarkers);
|
|
811
|
-
if (value && typeof value === 'object') {
|
|
812
|
-
return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== SECRET_SET).map(([k, v]) => [k, withoutMarkers(v)]));
|
|
813
|
-
}
|
|
814
|
-
return value;
|
|
815
|
-
}
|
|
816
|
-
/** An origin list in either form (strings, or { origin, integration }) as plain strings. */
|
|
817
|
-
function originStrings(list) {
|
|
818
|
-
return (Array.isArray(list) ? list : []).map((e) => (typeof e === 'string' ? e : String(obj(e).origin ?? '')));
|
|
819
|
-
}
|
|
820
|
-
/** auth_config without the integration hints, which belong to allowed_origins. */
|
|
821
|
-
function stripOriginIntegrations(authConfig) {
|
|
822
|
-
const { originIntegrations: _hints, ...rest } = obj(authConfig);
|
|
823
|
-
return rest;
|
|
824
|
-
}
|
|
825
|
-
/** A stable natural key for an SSO connection, which has no name field. */
|
|
826
|
-
function ssoKey(config) {
|
|
827
|
-
const c = obj(config);
|
|
828
|
-
const type = String(c.type ?? 'sso');
|
|
829
|
-
const id = c.oidc_issuer ?? c.saml_idp_entity_id ?? c.saml_sp_entity_id ?? c.discourse_provider_url ?? '';
|
|
830
|
-
return `${type}:${String(id)}`;
|
|
831
|
-
}
|
|
832
|
-
/** Configure an OAuth provider: credentials/scopes (PUT), then scope + enabled toggles. */
|
|
833
|
-
async function putProvider(http, config) {
|
|
834
|
-
const c = obj(config);
|
|
835
|
-
// Provider settings (a tenant, a domain, Apple's team/key ids) ride along in `values`.
|
|
836
|
-
const values = { ...obj(c.values ?? null) };
|
|
837
|
-
if (c.client_id !== undefined && c.client_id !== null)
|
|
838
|
-
values.client_id = c.client_id;
|
|
839
|
-
// A secret only exists here when the file carried a ${ENV} placeholder for it.
|
|
840
|
-
if (c.client_secret !== undefined && c.client_secret !== null) {
|
|
841
|
-
values.client_secret = c.client_secret;
|
|
842
|
-
}
|
|
843
|
-
const body = {};
|
|
844
|
-
if (Object.keys(values).length)
|
|
845
|
-
body.values = values;
|
|
846
|
-
if (Array.isArray(c.scopes))
|
|
847
|
-
body.scopes = c.scopes;
|
|
848
|
-
// Credential-less providers (siwe, siws, siwb, siwf) take verifier settings instead.
|
|
849
|
-
if (c.settings && typeof c.settings === 'object' && !Array.isArray(c.settings))
|
|
850
|
-
body.settings = c.settings;
|
|
851
|
-
const provider = String(c.provider ?? '');
|
|
852
|
-
if (Object.keys(body).length) {
|
|
853
|
-
await http.put(`/v1/oauth_providers/${enc(provider)}`, body);
|
|
854
|
-
}
|
|
855
|
-
if (c.allow_sign_in !== undefined || c.allow_sign_up !== undefined) {
|
|
856
|
-
await http.post(`/v1/oauth_providers/${enc(provider)}/scope`, {
|
|
857
|
-
allow_sign_in: c.allow_sign_in ?? true,
|
|
858
|
-
allow_sign_up: c.allow_sign_up ?? true,
|
|
859
|
-
});
|
|
860
|
-
}
|
|
861
|
-
if (c.enabled !== undefined) {
|
|
862
|
-
await http.post(`/v1/oauth_providers/${enc(provider)}/enabled`, { enabled: c.enabled });
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
async function putEmail(http, config) {
|
|
866
|
-
const c = obj(config);
|
|
867
|
-
const body = {};
|
|
868
|
-
if (c.subject !== undefined)
|
|
869
|
-
body.subject = c.subject;
|
|
870
|
-
if (c.text !== undefined)
|
|
871
|
-
body.text = c.text;
|
|
872
|
-
await http.put(`/v1/email_templates/${enc(String(c.name ?? ''))}`, body);
|
|
873
|
-
}
|
|
874
|
-
async function patchOrgPolicy(http, config) {
|
|
875
|
-
const c = obj(config);
|
|
876
|
-
const body = {};
|
|
877
|
-
for (const k of ORG_POLICY_KEYS) {
|
|
878
|
-
if (k === 'organization_id')
|
|
879
|
-
continue;
|
|
880
|
-
if (c[k] !== undefined && c[k] !== null)
|
|
881
|
-
body[k] = c[k];
|
|
882
|
-
}
|
|
883
|
-
await http.patch(`/v1/organizations/${enc(String(c.organization_id ?? ''))}/policy`, body);
|
|
884
|
-
}
|
|
885
|
-
// ───────────────────────────── the command ─────────────────────────────
|
|
886
|
-
/** `atlas config export|import` — Auth0 `deploy-cli`-style config-as-code. */
|
|
887
|
-
async function config(ctx, args) {
|
|
888
|
-
const sub = args.positionals[1];
|
|
889
|
-
switch (sub) {
|
|
890
|
-
case 'export':
|
|
891
|
-
return exportConfig(ctx, args.values);
|
|
892
|
-
case 'import':
|
|
893
|
-
return importConfig(ctx, args.values);
|
|
894
|
-
case 'plan':
|
|
895
|
-
return planConfig(ctx, args.values, { dir: resolveDir(args.values), format: resolveFormat(args.values), env: readSecretsEnv(ctx, args.values) });
|
|
896
|
-
case 'promote':
|
|
897
|
-
return promoteConfig(ctx, args.values);
|
|
898
|
-
default:
|
|
899
|
-
throw new errors_1.CliError(sub
|
|
900
|
-
? `Unknown subcommand 'config ${sub}'. Try: export, import, plan, promote.`
|
|
901
|
-
: 'config needs a subcommand: export, import, plan, promote.');
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
function resolveFormat(values) {
|
|
905
|
-
const raw = (0, args_1.str)(values, 'format');
|
|
906
|
-
if (raw === undefined)
|
|
907
|
-
return 'json';
|
|
908
|
-
if (raw !== 'json' && raw !== 'yaml') {
|
|
909
|
-
throw new errors_1.CliError(`--format must be 'json' or 'yaml', got '${raw}'.`);
|
|
910
|
-
}
|
|
911
|
-
return raw;
|
|
912
|
-
}
|
|
913
|
-
function resolveDir(values) {
|
|
914
|
-
return (0, args_1.str)(values, 'dir') || './atlas-config';
|
|
915
|
-
}
|
|
916
|
-
/** Build the config HTTP client from the resolved profile credential. */
|
|
917
|
-
function requireHttp(ctx, values) {
|
|
918
|
-
const auth = (0, shared_1.resolveAuth)(ctx, values);
|
|
919
|
-
const make = ctx.makeHttp ?? config_http_1.createConfigHttp;
|
|
920
|
-
return make({ secretKey: auth.secretKey, apiUrl: auth.apiUrl });
|
|
921
|
-
}
|
|
922
|
-
/** Write every resource type's live config to `dir` (deterministic, secrets omitted); returns the file count. */
|
|
923
|
-
async function writeExport(http, dir, format) {
|
|
924
|
-
const ext = format === 'json' ? 'json' : 'yaml';
|
|
925
|
-
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
926
|
-
let fileCount = 0;
|
|
927
|
-
for (const spec of registry()) {
|
|
928
|
-
if (spec.kind === 'singleton') {
|
|
929
|
-
const live = await spec.fetchLive(http);
|
|
930
|
-
if (live === null)
|
|
931
|
-
continue; // nothing to write (e.g. no schema registered)
|
|
932
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, `${spec.type}.${ext}`), (0, serialize_1.serialize)(withoutMarkers(live), format));
|
|
933
|
-
fileCount += 1;
|
|
934
|
-
}
|
|
935
|
-
else {
|
|
936
|
-
const entries = (await spec.fetchLive(http)).filter((e) => !e.managedBy);
|
|
937
|
-
// Deterministic: entries sorted by natural key, keys sorted inside each.
|
|
938
|
-
const sorted = [...entries].sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
939
|
-
const doc = sorted.map((e) => (0, serialize_1.canonicalize)(e.config));
|
|
940
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, `${spec.type}.${ext}`), (0, serialize_1.serialize)(doc, format));
|
|
941
|
-
fileCount += 1;
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
return fileCount;
|
|
945
|
-
}
|
|
946
|
-
async function exportConfig(ctx, values) {
|
|
947
|
-
const http = requireHttp(ctx, values);
|
|
948
|
-
const dir = resolveDir(values);
|
|
949
|
-
const format = resolveFormat(values);
|
|
950
|
-
const fileCount = await writeExport(http, dir, format);
|
|
951
|
-
ctx.io.out(`Exported ${fileCount} resource file(s) to ${dir} (${format}).`);
|
|
952
|
-
ctx.io.out('Secrets are never exported — supply any credential at import via a ${ENV} placeholder.');
|
|
953
|
-
return 0;
|
|
954
|
-
}
|
|
955
|
-
/**
|
|
956
|
-
* Diff a config directory against the live instance. Nothing is written: the
|
|
957
|
-
* returned changes carry their own `apply`. Used by `atlas config import` and by
|
|
958
|
-
* `atlas migrate better-auth` (which shows the plan before applying it).
|
|
959
|
-
*/
|
|
960
|
-
async function planConfigImport(http, opts) {
|
|
961
|
-
const { dir, format, env, prune } = opts;
|
|
962
|
-
const applyOpts = { forceHook: opts.forceHook === true };
|
|
963
|
-
const ext = format === 'json' ? 'json' : 'yaml';
|
|
964
|
-
if (!(0, node_fs_1.existsSync)(dir)) {
|
|
965
|
-
throw new errors_1.CliError(`Config directory not found: ${dir}. Run 'atlas config export' first.`);
|
|
966
|
-
}
|
|
967
|
-
const present = new Set((0, node_fs_1.readdirSync)(dir));
|
|
968
|
-
const changes = [];
|
|
969
|
-
let unchanged = 0;
|
|
970
|
-
const skippedDeletes = [];
|
|
971
|
-
const managed = [];
|
|
972
|
-
for (const spec of registry()) {
|
|
973
|
-
const file = `${spec.type}.${ext}`;
|
|
974
|
-
// A type with no file is simply NOT MANAGED — leave live untouched.
|
|
975
|
-
if (!present.has(file))
|
|
976
|
-
continue;
|
|
977
|
-
const text = (0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, file), 'utf8');
|
|
978
|
-
const parsed = interpolate((0, serialize_1.deserialize)(text, format), env);
|
|
979
|
-
if (spec.kind === 'singleton') {
|
|
980
|
-
const desired = parsed;
|
|
981
|
-
const liveRaw = await spec.fetchLive(http);
|
|
982
|
-
const norm = spec.normalize ?? ((c) => c);
|
|
983
|
-
const desiredN = norm(desired);
|
|
984
|
-
// A section that only clears fields (all null) against nothing live is already satisfied.
|
|
985
|
-
if (liveRaw === null && onlyNulls(desiredN)) {
|
|
986
|
-
unchanged += 1;
|
|
987
|
-
continue;
|
|
988
|
-
}
|
|
989
|
-
const live = liveRaw === null ? null : comparable(norm(liveRaw), desiredN, prune);
|
|
990
|
-
if (live !== null && (0, serialize_1.deepEqual)(desiredN, live)) {
|
|
991
|
-
unchanged += 1;
|
|
992
|
-
continue;
|
|
993
|
-
}
|
|
994
|
-
changes.push({ type: spec.type, action: live === null ? 'create' : 'update', key: '', ...(live === null ? {} : { before: live }), after: desiredN, apply: () => spec.apply(http, desired, applyOpts) });
|
|
995
|
-
continue;
|
|
996
|
-
}
|
|
997
|
-
// Collection: diff desired-by-key against live-by-key.
|
|
998
|
-
const desiredList = asArray(parsed);
|
|
999
|
-
const desiredByKey = new Map();
|
|
1000
|
-
for (const cfg of desiredList)
|
|
1001
|
-
desiredByKey.set(spec.keyOf(cfg), cfg);
|
|
1002
|
-
const all = await spec.fetchLive(http);
|
|
1003
|
-
for (const e of all)
|
|
1004
|
-
if (e.managedBy)
|
|
1005
|
-
managed.push({ type: spec.type, key: e.key, managedBy: e.managedBy });
|
|
1006
|
-
const live = all.filter((e) => !e.managedBy);
|
|
1007
|
-
const liveByKey = new Map(live.map((e) => [e.key, e]));
|
|
1008
|
-
for (const [key, cfg] of desiredByKey) {
|
|
1009
|
-
const existing = liveByKey.get(key);
|
|
1010
|
-
if (!existing) {
|
|
1011
|
-
changes.push({ type: spec.type, action: 'create', key, after: cfg, apply: () => spec.create(http, cfg) });
|
|
1012
|
-
}
|
|
1013
|
-
else if (!(0, serialize_1.deepEqual)(cfg, comparable(existing.config, cfg, prune))) {
|
|
1014
|
-
if (!spec.update) {
|
|
1015
|
-
throw new errors_1.CliError(`${spec.type} '${key}' changed but is immutable; recreate it instead.`);
|
|
1016
|
-
}
|
|
1017
|
-
const upd = spec.update;
|
|
1018
|
-
changes.push({ type: spec.type, action: 'update', key, before: comparable(existing.config, cfg, prune), after: cfg, apply: () => upd(http, existing, cfg) });
|
|
1019
|
-
}
|
|
1020
|
-
else {
|
|
1021
|
-
unchanged += 1;
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
for (const entry of live) {
|
|
1025
|
-
if (desiredByKey.has(entry.key))
|
|
1026
|
-
continue;
|
|
1027
|
-
const change = { type: spec.type, action: 'delete', key: entry.key, before: entry.config };
|
|
1028
|
-
if (!spec.remove) {
|
|
1029
|
-
skippedDeletes.push(change);
|
|
1030
|
-
continue;
|
|
1031
|
-
}
|
|
1032
|
-
if (prune) {
|
|
1033
|
-
const rm = spec.remove;
|
|
1034
|
-
changes.push({ ...change, apply: () => rm(http, entry) });
|
|
1035
|
-
}
|
|
1036
|
-
else {
|
|
1037
|
-
skippedDeletes.push(change);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
return { changes, unchanged, skippedDeletes, managed };
|
|
1042
|
-
}
|
|
1043
|
-
/** True for a non-empty object whose every leaf is `null` (a file that only clears fields). */
|
|
1044
|
-
function onlyNulls(x) {
|
|
1045
|
-
if (x === null)
|
|
1046
|
-
return true;
|
|
1047
|
-
if (typeof x !== 'object' || Array.isArray(x) || x === undefined)
|
|
1048
|
-
return false;
|
|
1049
|
-
const vals = Object.values(x);
|
|
1050
|
-
return vals.length > 0 && vals.every(onlyNulls);
|
|
1051
|
-
}
|
|
1052
|
-
/**
|
|
1053
|
-
* The live document as the plan compares it. With `--prune` the whole live
|
|
1054
|
-
* document (keys the file leaves unset show as drift). Without it only the keys
|
|
1055
|
-
* the file sets, recursively, so a freshly applied file plans clean. A desired
|
|
1056
|
-
* `null` means "unset", so it matches a live key that is absent.
|
|
1057
|
-
*/
|
|
1058
|
-
function comparable(live, desired, prune) {
|
|
1059
|
-
if (prune)
|
|
1060
|
-
return live;
|
|
1061
|
-
const isObj = (x) => x !== null && typeof x === 'object' && !Array.isArray(x);
|
|
1062
|
-
if (!isObj(live) || !isObj(desired))
|
|
1063
|
-
return live;
|
|
1064
|
-
const out = {};
|
|
1065
|
-
for (const k of Object.keys(desired)) {
|
|
1066
|
-
const l = live[k];
|
|
1067
|
-
if (l === undefined || l === null) {
|
|
1068
|
-
if (desired[k] === null)
|
|
1069
|
-
out[k] = null;
|
|
1070
|
-
continue;
|
|
1071
|
-
}
|
|
1072
|
-
out[k] = comparable(l, desired[k], false);
|
|
1073
|
-
}
|
|
1074
|
-
return out;
|
|
1075
|
-
}
|
|
1076
|
-
/** The stored credential of a named profile (one profile per environment). */
|
|
1077
|
-
function profileHttp(ctx, name) {
|
|
1078
|
-
const stored = ctx.config.load().profiles[name];
|
|
1079
|
-
if (!stored?.secret_key) {
|
|
1080
|
-
throw new errors_1.CliError(`No profile named '${name}'. Run 'atlas login --profile ${name}' with that environment's secret key.`);
|
|
1081
|
-
}
|
|
1082
|
-
const secretKey = stored.secret_key;
|
|
1083
|
-
const apiUrl = (0, shared_1.envApiUrl)(ctx.env) || stored.api_url || backend_1.DEFAULT_API_URL;
|
|
1084
|
-
const make = ctx.makeHttp ?? config_http_1.createConfigHttp;
|
|
1085
|
-
return { http: make({ secretKey, apiUrl }), secretKey };
|
|
1086
|
-
}
|
|
1087
|
-
/** True for a production (live) secret key: `atlas_live_sk_…` or `sk_live_…`. */
|
|
1088
|
-
const isLiveKey = (key) => /(?:^|_)live_/.test(key);
|
|
1089
|
-
/**
|
|
1090
|
-
* `atlas config promote --from <profile> --to <profile>`: copy one environment's
|
|
1091
|
-
* configuration to another (development → staging → production) through the same
|
|
1092
|
-
* plan/apply engine as `config import`. Each environment is a profile holding that
|
|
1093
|
-
* instance's secret key. Secrets never travel: `${ENV}` placeholders stay as they
|
|
1094
|
-
* are and credentials are set per environment. `--dry-run` prints the plan only;
|
|
1095
|
-
* applying to a live (production) key needs `--yes`.
|
|
1096
|
-
*/
|
|
1097
|
-
async function promoteConfig(ctx, values) {
|
|
1098
|
-
const from = (0, args_1.str)(values, 'from');
|
|
1099
|
-
const to = (0, args_1.str)(values, 'to');
|
|
1100
|
-
if (!from || !to)
|
|
1101
|
-
throw new errors_1.CliError('promote needs --from <profile> and --to <profile> (one profile per environment).');
|
|
1102
|
-
if (from === to)
|
|
1103
|
-
throw new errors_1.CliError('--from and --to name the same profile.');
|
|
1104
|
-
const format = resolveFormat(values);
|
|
1105
|
-
const dryRun = values['dry-run'] === true;
|
|
1106
|
-
const prune = values.prune === true;
|
|
1107
|
-
const source = profileHttp(ctx, from);
|
|
1108
|
-
const target = profileHttp(ctx, to);
|
|
1109
|
-
if (source.secretKey === target.secretKey) {
|
|
1110
|
-
throw new errors_1.CliError(`Profiles '${from}' and '${to}' hold the same secret key — they are the same environment.`);
|
|
1111
|
-
}
|
|
1112
|
-
if (!dryRun && isLiveKey(target.secretKey) && values.yes !== true) {
|
|
1113
|
-
throw new errors_1.CliError(`'${to}' is a live (production) environment. Review with --dry-run, then re-run with --yes to apply.`);
|
|
1114
|
-
}
|
|
1115
|
-
const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'atlas-promote-'));
|
|
1116
|
-
try {
|
|
1117
|
-
await writeExport(source.http, dir, format);
|
|
1118
|
-
const plan = await planConfigImport(target.http, { dir, format, env: ctx.env, prune });
|
|
1119
|
-
ctx.io.out(`Promoting ${from} -> ${to}`);
|
|
1120
|
-
printPlan(ctx, plan.changes, plan.unchanged, plan.skippedDeletes, { dryRun, prune });
|
|
1121
|
-
if (dryRun)
|
|
1122
|
-
return 0;
|
|
1123
|
-
for (const change of plan.changes)
|
|
1124
|
-
await change.apply();
|
|
1125
|
-
ctx.io.out(plan.changes.length ? `Applied ${plan.changes.length} change(s) to ${to}.` : 'No changes to apply.');
|
|
1126
|
-
return 0;
|
|
1127
|
-
}
|
|
1128
|
-
finally {
|
|
1129
|
-
(0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
async function importConfig(ctx, values) {
|
|
1133
|
-
const http = requireHttp(ctx, values);
|
|
1134
|
-
const dir = resolveDir(values);
|
|
1135
|
-
const format = resolveFormat(values);
|
|
1136
|
-
const dryRun = values['dry-run'] === true;
|
|
1137
|
-
const prune = values.prune === true;
|
|
1138
|
-
const plan = await planConfigImport(http, { dir, format, env: readSecretsEnv(ctx, values), prune, forceHook: values['force-hook'] === true });
|
|
1139
|
-
printPlan(ctx, plan.changes, plan.unchanged, plan.skippedDeletes, { dryRun, prune });
|
|
1140
|
-
if (dryRun)
|
|
1141
|
-
return 0;
|
|
1142
|
-
for (const change of plan.changes)
|
|
1143
|
-
await change.apply();
|
|
1144
|
-
if (plan.changes.length)
|
|
1145
|
-
ctx.io.out(`Applied ${plan.changes.length} change(s).`);
|
|
1146
|
-
else
|
|
1147
|
-
ctx.io.out('No changes to apply.');
|
|
1148
|
-
return 0;
|
|
1149
|
-
}
|
|
1150
|
-
/** Leaf-level differences between two documents, as dotted paths (arrays compare whole). */
|
|
1151
|
-
function diffFields(before, after, path = '') {
|
|
1152
|
-
const isObj = (x) => x !== null && typeof x === 'object' && !Array.isArray(x);
|
|
1153
|
-
if (isObj(before) && isObj(after)) {
|
|
1154
|
-
const keys = [...new Set([...Object.keys(before), ...Object.keys(after)])].sort();
|
|
1155
|
-
return keys.flatMap((k) => diffFields(before[k], after[k], path ? `${path}.${k}` : k));
|
|
1156
|
-
}
|
|
1157
|
-
if (before !== undefined && after !== undefined && (0, serialize_1.deepEqual)(before, after))
|
|
1158
|
-
return [];
|
|
1159
|
-
if (before === undefined && after === undefined)
|
|
1160
|
-
return [];
|
|
1161
|
-
return [{ path: path || '(document)', ...(before === undefined ? {} : { before }), ...(after === undefined ? {} : { after }) }];
|
|
1162
|
-
}
|
|
1163
|
-
const show = (v) => (v === undefined ? '(unset)' : typeof v === 'string' ? JSON.stringify(v) : JSON.stringify(v));
|
|
1164
|
-
/**
|
|
1165
|
-
* `atlas config plan`: a Terraform-style, read-only diff of a config directory
|
|
1166
|
-
* (`atlas config export` output, committed to git) against the live instance.
|
|
1167
|
-
* Field-level for updates, `--json` for tooling, and `--exit-code` to exit 2
|
|
1168
|
-
* when there is drift so CI can gate on it. Never writes anything.
|
|
1169
|
-
*/
|
|
1170
|
-
async function planConfig(ctx, values, source) {
|
|
1171
|
-
const http = requireHttp(ctx, values);
|
|
1172
|
-
const { dir, format, env } = source;
|
|
1173
|
-
const where = source.label ?? dir;
|
|
1174
|
-
const prune = values.prune === true;
|
|
1175
|
-
const plan = await planConfigImport(http, { dir, format, env, prune });
|
|
1176
|
-
const deletes = prune ? [] : plan.skippedDeletes;
|
|
1177
|
-
const all = plan.changes;
|
|
1178
|
-
const rows = all.map((c) => ({
|
|
1179
|
-
type: c.type,
|
|
1180
|
-
key: c.key || null,
|
|
1181
|
-
action: c.action,
|
|
1182
|
-
fields: c.action === 'update' ? diffFields(c.before, c.after) : [],
|
|
1183
|
-
}));
|
|
1184
|
-
const pending = rows.length;
|
|
1185
|
-
if (values.json === true) {
|
|
1186
|
-
ctx.io.out(JSON.stringify({
|
|
1187
|
-
object: 'config_plan',
|
|
1188
|
-
changes: rows,
|
|
1189
|
-
unchanged: plan.unchanged,
|
|
1190
|
-
unmanaged_live: deletes.map((d) => ({ type: d.type, key: d.key || null })),
|
|
1191
|
-
managed_by_platform: plan.managed.map((m) => ({ type: m.type, key: m.key, managed_by: m.managedBy })),
|
|
1192
|
-
has_changes: pending > 0,
|
|
1193
|
-
}, null, 2));
|
|
1194
|
-
}
|
|
1195
|
-
else {
|
|
1196
|
-
ctx.io.out(`Plan: ${pending} to change, ${plan.unchanged} unchanged${deletes.length ? `, ${deletes.length} live but not in config` : ''}.`);
|
|
1197
|
-
const sign = { create: '+', update: '~', delete: '-' };
|
|
1198
|
-
for (const c of [...all].sort(sortChange)) {
|
|
1199
|
-
const label = c.key ? `${c.type} ${c.key}` : c.type;
|
|
1200
|
-
ctx.io.out('');
|
|
1201
|
-
ctx.io.out(`${sign[c.action]} ${label} (${c.action})`);
|
|
1202
|
-
if (c.action === 'update') {
|
|
1203
|
-
for (const f of diffFields(c.before, c.after))
|
|
1204
|
-
ctx.io.out(` ${f.path}: ${show(f.before)} -> ${show(f.after)}`);
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
if (deletes.length) {
|
|
1208
|
-
ctx.io.out('');
|
|
1209
|
-
ctx.io.out(`Live but not in ${where} (kept; pass --prune to plan their deletion):`);
|
|
1210
|
-
for (const d of deletes)
|
|
1211
|
-
ctx.io.out(` ${d.type}${d.key ? ` ${d.key}` : ''}`);
|
|
1212
|
-
}
|
|
1213
|
-
if (plan.managed.length) {
|
|
1214
|
-
ctx.io.out('');
|
|
1215
|
-
ctx.io.out('Managed by a connected platform (not part of this file; never pruned):');
|
|
1216
|
-
for (const m of plan.managed)
|
|
1217
|
-
ctx.io.out(` ${m.type} ${m.key} (${m.managedBy})`);
|
|
1218
|
-
}
|
|
1219
|
-
if (pending === 0)
|
|
1220
|
-
ctx.io.out(`No drift: the instance matches ${where}.`);
|
|
1221
|
-
}
|
|
1222
|
-
return wantsCheck(values) && pending > 0 ? 2 : 0;
|
|
1223
|
-
}
|
|
1224
|
-
/** `--check` (CI) and `--exit-code` (Terraform's name) both mean: exit 2 when there is drift. */
|
|
1225
|
-
const wantsCheck = (values) => values['exit-code'] === true || values.check === true;
|
|
1226
|
-
/**
|
|
1227
|
-
* The environment `${VAR}` placeholders resolve against: the process env, then
|
|
1228
|
-
* `--secrets-file` (a dotenv file) on top. The file holds credentials, so it
|
|
1229
|
-
* must be readable by its owner only (mode 0600 or stricter); anything looser
|
|
1230
|
-
* is refused rather than read. POSIX only: Windows has no such mode bits.
|
|
1231
|
-
*/
|
|
1232
|
-
function readSecretsEnv(ctx, values) {
|
|
1233
|
-
const file = (0, args_1.str)(values, 'secrets-file');
|
|
1234
|
-
if (!file)
|
|
1235
|
-
return ctx.env;
|
|
1236
|
-
let mode;
|
|
1237
|
-
try {
|
|
1238
|
-
mode = (0, node_fs_1.statSync)(file).mode;
|
|
1239
|
-
}
|
|
1240
|
-
catch {
|
|
1241
|
-
throw new errors_1.CliError(`--secrets-file not found: ${file}.`);
|
|
1242
|
-
}
|
|
1243
|
-
if (process.platform !== 'win32' && (mode & 0o077) !== 0) {
|
|
1244
|
-
throw new errors_1.CliError(`--secrets-file ${file} is readable by others (mode ${(mode & 0o777).toString(8)}). Run 'chmod 600 ${file}' and try again.`);
|
|
1245
|
-
}
|
|
1246
|
-
return { ...ctx.env, ...(0, secrets_1.parseDotenv)(ctx.readFile(file)) };
|
|
1247
|
-
}
|
|
1248
|
-
/** `${VAR}` placeholders still in a planned change (no env value and no --secrets-file entry). */
|
|
1249
|
-
function unresolvedPlaceholders(changes) {
|
|
1250
|
-
const names = new Set();
|
|
1251
|
-
for (const c of changes) {
|
|
1252
|
-
for (const m of JSON.stringify(c.after ?? null).matchAll(/\$\{([A-Z0-9_]+)\}/g))
|
|
1253
|
-
names.add(m[1]);
|
|
1254
|
-
}
|
|
1255
|
-
return [...names].sort();
|
|
1256
|
-
}
|
|
1257
|
-
/** The resource types a single config file may hold, in the order they are applied. */
|
|
1258
|
-
function configTypes() {
|
|
1259
|
-
return registry().map((s) => s.type);
|
|
1260
|
-
}
|
|
1261
|
-
/**
|
|
1262
|
-
* Resolve `atlas plan|apply [<path>]`: a directory of per-type files (as
|
|
1263
|
-
* `atlas config export` writes), or ONE file whose top-level keys are resource
|
|
1264
|
-
* types (`instance`, `webhook_endpoints`, …), split into a temp directory.
|
|
1265
|
-
* `cleanup` removes that temp directory.
|
|
1266
|
-
*/
|
|
1267
|
-
function resolveSource(ctx, args) {
|
|
1268
|
-
const path = args.positionals[1] ?? resolveDir(args.values);
|
|
1269
|
-
const env = readSecretsEnv(ctx, args.values);
|
|
1270
|
-
let isFile = false;
|
|
1271
|
-
try {
|
|
1272
|
-
isFile = (0, node_fs_1.statSync)(path).isFile();
|
|
1273
|
-
}
|
|
1274
|
-
catch {
|
|
1275
|
-
throw new errors_1.CliError(`Config not found: ${path}. Pass a config file (atlas.yaml / atlas.json) or a directory from 'atlas config export'.`);
|
|
1276
|
-
}
|
|
1277
|
-
if (!isFile)
|
|
1278
|
-
return { dir: path, label: path, format: resolveFormat(args.values), env, cleanup: () => undefined };
|
|
1279
|
-
const ext = (0, node_path_1.extname)(path).toLowerCase();
|
|
1280
|
-
const format = ext === '.yaml' || ext === '.yml' ? 'yaml' : ext === '.json' ? 'json' : resolveFormat(args.values);
|
|
1281
|
-
const doc = (0, serialize_1.deserialize)(ctx.readFile(path), format);
|
|
1282
|
-
if (doc === null || typeof doc !== 'object' || Array.isArray(doc)) {
|
|
1283
|
-
throw new errors_1.CliError(`${path} must be an object keyed by resource type (${configTypes().join(', ')}).`);
|
|
1284
|
-
}
|
|
1285
|
-
const known = new Set(configTypes());
|
|
1286
|
-
const unknown = Object.keys(doc).filter((k) => !known.has(k));
|
|
1287
|
-
if (unknown.length) {
|
|
1288
|
-
throw new errors_1.CliError(`${path}: unknown resource type(s) ${unknown.join(', ')}. Known: ${configTypes().join(', ')}.`);
|
|
1289
|
-
}
|
|
1290
|
-
const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'atlas-apply-'));
|
|
1291
|
-
for (const [type, value] of Object.entries(doc))
|
|
1292
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, `${type}.json`), (0, serialize_1.serialize)(value, 'json'));
|
|
1293
|
-
return { dir, label: path, format: 'json', env, cleanup: () => (0, node_fs_1.rmSync)(dir, { recursive: true, force: true }) };
|
|
1294
|
-
}
|
|
1295
|
-
/**
|
|
1296
|
-
* `atlas plan [<file|dir>]`: the read-only diff of `atlas config plan`, taking
|
|
1297
|
-
* a single config file as well. `--check` exits 2 on drift for CI.
|
|
1298
|
-
*/
|
|
1299
|
-
async function planCommand(ctx, args) {
|
|
1300
|
-
const source = resolveSource(ctx, args);
|
|
1301
|
-
try {
|
|
1302
|
-
return await planConfig(ctx, args.values, source);
|
|
1303
|
-
}
|
|
1304
|
-
finally {
|
|
1305
|
-
source.cleanup();
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
/**
|
|
1309
|
-
* `atlas apply [<file|dir>]`: plan, show it, confirm, then apply in the fixed
|
|
1310
|
-
* resource order (see {@link configTypes}). `--yes` skips the question (needed
|
|
1311
|
-
* when there is no terminal); `--check` only plans and exits 2 on drift.
|
|
1312
|
-
* Refuses to apply while any `${VAR}` placeholder is unresolved, so a missing
|
|
1313
|
-
* secret is never sent as the literal text.
|
|
1314
|
-
*/
|
|
1315
|
-
async function applyCommand(ctx, args) {
|
|
1316
|
-
const values = args.values;
|
|
1317
|
-
if (values.check === true || values['exit-code'] === true)
|
|
1318
|
-
return planCommand(ctx, args);
|
|
1319
|
-
const source = resolveSource(ctx, args);
|
|
1320
|
-
try {
|
|
1321
|
-
const kept = keepingSecrets(requireHttp(ctx, values), (0, args_1.str)(values, 'out') || (0, secrets_1.productDir)(ctx.env, 'atlas'));
|
|
1322
|
-
const prune = values.prune === true;
|
|
1323
|
-
const plan = await planConfigImport(kept.http, { dir: source.dir, format: source.format, env: source.env, prune, forceHook: values['force-hook'] === true });
|
|
1324
|
-
printPlan(ctx, plan.changes, plan.unchanged, plan.skippedDeletes, { dryRun: false, prune });
|
|
1325
|
-
if (plan.changes.length === 0) {
|
|
1326
|
-
ctx.io.out('No changes to apply.');
|
|
1327
|
-
return 0;
|
|
1328
|
-
}
|
|
1329
|
-
const missing = unresolvedPlaceholders(plan.changes);
|
|
1330
|
-
if (missing.length) {
|
|
1331
|
-
throw new errors_1.CliError(`Unresolved placeholder(s): ${missing.map((n) => `\${${n}}`).join(', ')}. Set them in the environment or in --secrets-file.`);
|
|
1332
|
-
}
|
|
1333
|
-
if (values.yes !== true) {
|
|
1334
|
-
const answer = (await ctx.prompt.text(`Apply ${plan.changes.length} change(s)? [y/N]`, { default: 'n' })).trim().toLowerCase();
|
|
1335
|
-
if (answer !== 'y' && answer !== 'yes') {
|
|
1336
|
-
ctx.io.out('Nothing applied.');
|
|
1337
|
-
return 1;
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
for (const change of plan.changes)
|
|
1341
|
-
await change.apply();
|
|
1342
|
-
ctx.io.out(`Applied ${plan.changes.length} change(s).`);
|
|
1343
|
-
for (const path of kept.saved)
|
|
1344
|
-
ctx.io.out(`A new secret was written to ${path} (mode 0600). It is shown only once.`);
|
|
1345
|
-
return 0;
|
|
1346
|
-
}
|
|
1347
|
-
finally {
|
|
1348
|
-
source.cleanup();
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
/**
|
|
1352
|
-
* The API, but a create that answers with a one-time secret (a new OAuth
|
|
1353
|
-
* client's `client_secret`, a new webhook endpoint's `secret`) writes it to a
|
|
1354
|
-
* 0600 dotenv file in `dir` (default `~/.config/atlas`, or `--out`) instead of
|
|
1355
|
-
* dropping it. The file names are the hosted-redirect env names.
|
|
1356
|
-
*/
|
|
1357
|
-
function keepingSecrets(http, dir) {
|
|
1358
|
-
const saved = [];
|
|
1359
|
-
const wrapped = {
|
|
1360
|
-
get: (path) => http.get(path),
|
|
1361
|
-
put: (path, body) => http.put(path, body),
|
|
1362
|
-
patch: (path, body) => http.patch(path, body),
|
|
1363
|
-
delete: (path, body) => http.delete(path, body),
|
|
1364
|
-
async post(path, body) {
|
|
1365
|
-
const res = await http.post(path, body);
|
|
1366
|
-
const r = (res ?? {});
|
|
1367
|
-
const id = String(r.id ?? r.client_id ?? 'new').replace(/[^A-Za-z0-9_-]/g, '_');
|
|
1368
|
-
let entries = null;
|
|
1369
|
-
if (path === '/v1/oauth_clients' && typeof r.client_secret === 'string') {
|
|
1370
|
-
entries = { ATLAS_CLIENT_ID: String(r.client_id ?? ''), ATLAS_CLIENT_SECRET: r.client_secret };
|
|
1371
|
-
}
|
|
1372
|
-
else if (path === '/v1/webhook_endpoints' && typeof r.secret === 'string') {
|
|
1373
|
-
entries = { ATLAS_WEBHOOK_SECRET: r.secret };
|
|
1374
|
-
}
|
|
1375
|
-
if (entries) {
|
|
1376
|
-
const kind = path === '/v1/oauth_clients' ? 'oauth-client' : 'webhook';
|
|
1377
|
-
saved.push((0, secrets_1.writeSecretFile)(dir, `${kind}-${id}.env`, entries, { comment: `Written by atlas apply. ${kind} ${id}.` }).path);
|
|
1378
|
-
}
|
|
1379
|
-
return res;
|
|
1380
|
-
},
|
|
1381
|
-
};
|
|
1382
|
-
return { http: wrapped, saved };
|
|
1383
|
-
}
|
|
1384
|
-
function printPlan(ctx, changes, unchanged, skippedDeletes, opts) {
|
|
1385
|
-
const heading = opts.dryRun ? 'Plan (dry run — nothing applied):' : 'Plan:';
|
|
1386
|
-
ctx.io.out(heading);
|
|
1387
|
-
if (changes.length === 0) {
|
|
1388
|
-
ctx.io.out(' (no changes)');
|
|
1389
|
-
}
|
|
1390
|
-
const sign = { create: '+', update: '~', delete: '-' };
|
|
1391
|
-
for (const c of [...changes].sort(sortChange)) {
|
|
1392
|
-
const label = c.key ? `${c.type} ${c.key}` : c.type;
|
|
1393
|
-
ctx.io.out(` ${sign[c.action]} ${c.action} ${label}`);
|
|
1394
|
-
}
|
|
1395
|
-
if (!opts.prune && skippedDeletes.length) {
|
|
1396
|
-
ctx.io.out(` ${skippedDeletes.length} resource(s) exist live but not in config — pass --prune to delete.`);
|
|
1397
|
-
}
|
|
1398
|
-
ctx.io.out(` ${unchanged} unchanged.`);
|
|
1399
|
-
}
|
|
1400
|
-
function sortChange(a, b) {
|
|
1401
|
-
if (a.type !== b.type)
|
|
1402
|
-
return a.type < b.type ? -1 : 1;
|
|
1403
|
-
if (a.action !== b.action)
|
|
1404
|
-
return a.action < b.action ? -1 : 1;
|
|
1405
|
-
return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
|
|
1406
|
-
}
|
|
1407
|
-
function asArray(value) {
|
|
1408
|
-
return Array.isArray(value) ? value : [];
|
|
1409
|
-
}
|
|
1410
|
-
/**
|
|
1411
|
-
* Replace `${ENV_VAR}` placeholders inside any string with the environment's
|
|
1412
|
-
* value — the one sanctioned way a secret enters an import without ever living
|
|
1413
|
-
* on disk. An unset variable is left verbatim so the plan surfaces it rather
|
|
1414
|
-
* than silently blanking a credential.
|
|
1415
|
-
*/
|
|
1416
|
-
function interpolate(value, env) {
|
|
1417
|
-
if (typeof value === 'string') {
|
|
1418
|
-
return value.replace(/\$\{([A-Z0-9_]+)\}/g, (match, name) => env[name] ?? match);
|
|
1419
|
-
}
|
|
1420
|
-
if (Array.isArray(value))
|
|
1421
|
-
return value.map((v) => interpolate(v, env));
|
|
1422
|
-
if (value !== null && typeof value === 'object') {
|
|
1423
|
-
const out = {};
|
|
1424
|
-
for (const [k, v] of Object.entries(value))
|
|
1425
|
-
out[k] = interpolate(v, env);
|
|
1426
|
-
return out;
|
|
1427
|
-
}
|
|
1428
|
-
return value;
|
|
1429
|
-
}
|
|
1430
|
-
//# sourceMappingURL=config.js.map
|