primitive-admin 1.2.0-alpha.1 → 1.2.0-alpha.10
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/README.md +105 -134
- package/assets/skill/skills/primitive-platform/SKILL.md +37 -4
- package/dist/bin/primitive.js +30 -35
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/agent-sessions.d.ts +133 -0
- package/dist/src/commands/agent-sessions.js +792 -0
- package/dist/src/commands/agent-sessions.js.map +1 -0
- package/dist/src/commands/analytics.js +0 -148
- package/dist/src/commands/analytics.js.map +1 -1
- package/dist/src/commands/apps-children.d.ts +54 -0
- package/dist/src/commands/apps-children.js +631 -0
- package/dist/src/commands/apps-children.js.map +1 -0
- package/dist/src/commands/apps.d.ts +6 -0
- package/dist/src/commands/apps.js +131 -9
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/auth-sessions.d.ts +3 -3
- package/dist/src/commands/auth-sessions.js +117 -23
- package/dist/src/commands/auth-sessions.js.map +1 -1
- package/dist/src/commands/auth.js +460 -36
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/collections.js +316 -256
- package/dist/src/commands/collections.js.map +1 -1
- package/dist/src/commands/config.d.ts +1 -1
- package/dist/src/commands/config.js +60 -28
- package/dist/src/commands/config.js.map +1 -1
- package/dist/src/commands/connections.js +11 -12
- package/dist/src/commands/connections.js.map +1 -1
- package/dist/src/commands/database-type-configs.js +2 -76
- package/dist/src/commands/database-type-configs.js.map +1 -1
- package/dist/src/commands/databases.d.ts +9 -0
- package/dist/src/commands/databases.js +221 -457
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/db3-types.d.ts +15 -0
- package/dist/src/commands/db3-types.js +157 -0
- package/dist/src/commands/db3-types.js.map +1 -0
- package/dist/src/commands/db3.d.ts +12 -0
- package/dist/src/commands/db3.js +1052 -0
- package/dist/src/commands/db3.js.map +1 -0
- package/dist/src/commands/documents.d.ts +121 -2
- package/dist/src/commands/documents.js +1601 -1251
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/env.d.ts +9 -1
- package/dist/src/commands/env.js +164 -49
- package/dist/src/commands/env.js.map +1 -1
- package/dist/src/commands/function-trigger-commands.d.ts +28 -0
- package/dist/src/commands/function-trigger-commands.js +341 -0
- package/dist/src/commands/function-trigger-commands.js.map +1 -0
- package/dist/src/commands/functions.js +297 -24
- package/dist/src/commands/functions.js.map +1 -1
- package/dist/src/commands/groups.js +150 -49
- package/dist/src/commands/groups.js.map +1 -1
- package/dist/src/commands/guides.js +5 -5
- package/dist/src/commands/init.d.ts +7 -0
- package/dist/src/commands/init.js +23 -5
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.js +14 -8
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/metadata-category-configs.d.ts +2 -2
- package/dist/src/commands/metadata-category-configs.js +2 -2
- package/dist/src/commands/prompts.d.ts +7 -0
- package/dist/src/commands/prompts.js +48 -5
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/scripts.d.ts +9 -17
- package/dist/src/commands/scripts.js +24 -479
- package/dist/src/commands/scripts.js.map +1 -1
- package/dist/src/commands/sync-app-settings.d.ts +36 -8
- package/dist/src/commands/sync-app-settings.js +109 -23
- package/dist/src/commands/sync-app-settings.js.map +1 -1
- package/dist/src/commands/sync.d.ts +360 -610
- package/dist/src/commands/sync.js +3151 -6697
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.js +3 -3
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/users.js +77 -2
- package/dist/src/commands/users.js.map +1 -1
- package/dist/src/commands/workflows.d.ts +5 -83
- package/dist/src/commands/workflows.js +57 -2282
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/aggregate-output.d.ts +25 -0
- package/dist/src/lib/aggregate-output.js +53 -0
- package/dist/src/lib/aggregate-output.js.map +1 -0
- package/dist/src/lib/api-client.d.ts +681 -551
- package/dist/src/lib/api-client.js +880 -814
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/app-settings-descriptor.d.ts +47 -1
- package/dist/src/lib/app-settings-descriptor.js +114 -10
- package/dist/src/lib/app-settings-descriptor.js.map +1 -1
- package/dist/src/lib/auth-fixtures.d.ts +44 -0
- package/dist/src/lib/auth-fixtures.js +61 -0
- package/dist/src/lib/auth-fixtures.js.map +1 -0
- package/dist/src/lib/auth-flow.d.ts +41 -1
- package/dist/src/lib/auth-flow.js +70 -16
- package/dist/src/lib/auth-flow.js.map +1 -1
- package/dist/src/lib/block-layout.d.ts +12 -23
- package/dist/src/lib/block-layout.js +26 -92
- package/dist/src/lib/block-layout.js.map +1 -1
- package/dist/src/lib/block-selector.d.ts +2 -2
- package/dist/src/lib/block-selector.js +2 -2
- package/dist/src/lib/child-apps-local.d.ts +164 -0
- package/dist/src/lib/child-apps-local.js +258 -0
- package/dist/src/lib/child-apps-local.js.map +1 -0
- package/dist/src/lib/ci-session-request.d.ts +22 -0
- package/dist/src/lib/ci-session-request.js +51 -0
- package/dist/src/lib/ci-session-request.js.map +1 -0
- package/dist/src/lib/codegen-shared/generatedFiles.d.ts +10 -8
- package/dist/src/lib/codegen-shared/generatedFiles.js +21 -15
- package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
- package/dist/src/lib/codegen-shared/prettierStable.d.ts +1 -1
- package/dist/src/lib/codegen-shared/prettierStable.js +1 -1
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +2 -2
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +2 -2
- package/dist/src/lib/collection-export.d.ts +76 -15
- package/dist/src/lib/collection-export.js +116 -23
- package/dist/src/lib/collection-export.js.map +1 -1
- package/dist/src/lib/collection-principal.d.ts +22 -0
- package/dist/src/lib/collection-principal.js +35 -0
- package/dist/src/lib/collection-principal.js.map +1 -0
- package/dist/src/lib/config-object-descriptor.d.ts +3 -22
- package/dist/src/lib/config-object-descriptor.js +56 -290
- package/dist/src/lib/config-object-descriptor.js.map +1 -1
- package/dist/src/lib/config-payload.js +2 -3
- package/dist/src/lib/config-payload.js.map +1 -1
- package/dist/src/lib/config-surface.d.ts +5 -7
- package/dist/src/lib/config-surface.js +18 -40
- package/dist/src/lib/config-surface.js.map +1 -1
- package/dist/src/lib/connections-list.d.ts +10 -0
- package/dist/src/lib/connections-list.js +18 -0
- package/dist/src/lib/connections-list.js.map +1 -0
- package/dist/src/lib/credentials-home.d.ts +58 -0
- package/dist/src/lib/credentials-home.js +204 -0
- package/dist/src/lib/credentials-home.js.map +1 -0
- package/dist/src/lib/credentials-lock.d.ts +88 -0
- package/dist/src/lib/credentials-lock.js +302 -0
- package/dist/src/lib/credentials-lock.js.map +1 -0
- package/dist/src/lib/credentials-store.d.ts +179 -24
- package/dist/src/lib/credentials-store.js +712 -77
- package/dist/src/lib/credentials-store.js.map +1 -1
- package/dist/src/lib/csv.d.ts +8 -9
- package/dist/src/lib/csv.js +3 -4
- package/dist/src/lib/csv.js.map +1 -1
- package/dist/src/lib/custodian.d.ts +8 -0
- package/dist/src/lib/custodian.js +18 -0
- package/dist/src/lib/custodian.js.map +1 -0
- package/dist/src/lib/db-codegen/dbGenerator.js +5 -16
- package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -1
- package/dist/src/lib/db-codegen/dbNaming.d.ts +5 -13
- package/dist/src/lib/db-codegen/dbNaming.js +5 -17
- package/dist/src/lib/db-codegen/dbNaming.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTemplates.d.ts +7 -56
- package/dist/src/lib/db-codegen/dbTemplates.js +25 -125
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTsTypes.d.ts +2 -2
- package/dist/src/lib/db-codegen/dbTsTypes.js +2 -2
- package/dist/src/lib/db-codegen/dbTypeIR.d.ts +67 -2
- package/dist/src/lib/db-codegen/dbTypeIR.js +13 -2
- package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -1
- package/dist/src/lib/db3-export.d.ts +231 -0
- package/dist/src/lib/db3-export.js +510 -0
- package/dist/src/lib/db3-export.js.map +1 -0
- package/dist/src/lib/db3-index-output.d.ts +27 -0
- package/dist/src/lib/db3-index-output.js +49 -0
- package/dist/src/lib/db3-index-output.js.map +1 -0
- package/dist/src/lib/document-export-lookups.d.ts +31 -0
- package/dist/src/lib/document-export-lookups.js +53 -0
- package/dist/src/lib/document-export-lookups.js.map +1 -0
- package/dist/src/lib/document-export-permissions.d.ts +7 -5
- package/dist/src/lib/document-export-permissions.js +7 -5
- package/dist/src/lib/document-export-permissions.js.map +1 -1
- package/dist/src/lib/document-export-record-log.d.ts +57 -0
- package/dist/src/lib/document-export-record-log.js +215 -0
- package/dist/src/lib/document-export-record-log.js.map +1 -0
- package/dist/src/lib/document-ingest-artifact.d.ts +26 -2
- package/dist/src/lib/document-ingest-artifact.js +42 -8
- package/dist/src/lib/document-ingest-artifact.js.map +1 -1
- package/dist/src/lib/document-ingest-format2.d.ts +36 -0
- package/dist/src/lib/document-ingest-format2.js +78 -0
- package/dist/src/lib/document-ingest-format2.js.map +1 -0
- package/dist/src/lib/document-ingest-input.d.ts +9 -0
- package/dist/src/lib/document-ingest-input.js +28 -1
- package/dist/src/lib/document-ingest-input.js.map +1 -1
- package/dist/src/lib/document-ingest-rows.d.ts +16 -0
- package/dist/src/lib/document-ingest-rows.js +17 -0
- package/dist/src/lib/document-ingest-rows.js.map +1 -1
- package/dist/src/lib/document-ingest.d.ts +33 -1
- package/dist/src/lib/document-ingest.js +49 -0
- package/dist/src/lib/document-ingest.js.map +1 -1
- package/dist/src/lib/document-schema-rows.d.ts +42 -0
- package/dist/src/lib/document-schema-rows.js +53 -0
- package/dist/src/lib/document-schema-rows.js.map +1 -0
- package/dist/src/lib/env-resolver-core.d.ts +70 -2
- package/dist/src/lib/env-resolver-core.js +137 -20
- package/dist/src/lib/env-resolver-core.js.map +1 -1
- package/dist/src/lib/env-resolver.d.ts +14 -2
- package/dist/src/lib/env-resolver.js +32 -10
- package/dist/src/lib/env-resolver.js.map +1 -1
- package/dist/src/lib/function-authorize-check.d.ts +142 -0
- package/dist/src/lib/function-authorize-check.js +310 -0
- package/dist/src/lib/function-authorize-check.js.map +1 -0
- package/dist/src/lib/function-authorize-view.d.ts +15 -0
- package/dist/src/lib/function-authorize-view.js +50 -0
- package/dist/src/lib/function-authorize-view.js.map +1 -0
- package/dist/src/lib/function-bundle.d.ts +38 -18
- package/dist/src/lib/function-bundle.js +96 -35
- package/dist/src/lib/function-bundle.js.map +1 -1
- package/dist/src/lib/function-collect.d.ts +75 -12
- package/dist/src/lib/function-collect.js +427 -12
- package/dist/src/lib/function-collect.js.map +1 -1
- package/dist/src/lib/function-db-types.d.ts +29 -0
- package/dist/src/lib/function-db-types.js +117 -7
- package/dist/src/lib/function-db-types.js.map +1 -1
- package/dist/src/lib/function-document-types.d.ts +5 -3
- package/dist/src/lib/function-document-types.js +10 -14
- package/dist/src/lib/function-document-types.js.map +1 -1
- package/dist/src/lib/function-lint.d.ts +34 -0
- package/dist/src/lib/function-lint.js +112 -0
- package/dist/src/lib/function-lint.js.map +1 -0
- package/dist/src/lib/function-log-row.js +21 -2
- package/dist/src/lib/function-log-row.js.map +1 -1
- package/dist/src/lib/function-run.d.ts +16 -21
- package/dist/src/lib/function-run.js +37 -30
- package/dist/src/lib/function-run.js.map +1 -1
- package/dist/src/lib/function-schema-codegen.d.ts +1 -1
- package/dist/src/lib/function-schema-codegen.js +5 -5
- package/dist/src/lib/function-schema-codegen.js.map +1 -1
- package/dist/src/lib/function-source-roots.d.ts +67 -0
- package/dist/src/lib/function-source-roots.js +148 -0
- package/dist/src/lib/function-source-roots.js.map +1 -0
- package/dist/src/lib/function-sync.d.ts +108 -22
- package/dist/src/lib/function-sync.js +127 -39
- package/dist/src/lib/function-sync.js.map +1 -1
- package/dist/src/lib/function-trigger-listing.d.ts +11 -6
- package/dist/src/lib/function-trigger-listing.js +32 -11
- package/dist/src/lib/function-trigger-listing.js.map +1 -1
- package/dist/src/lib/function-typecheck.d.ts +37 -2
- package/dist/src/lib/function-typecheck.js +210 -28
- package/dist/src/lib/function-typecheck.js.map +1 -1
- package/dist/src/lib/function-versions.d.ts +2 -0
- package/dist/src/lib/function-versions.js +1 -0
- package/dist/src/lib/function-versions.js.map +1 -1
- package/dist/src/lib/generated-allowlist.js +0 -20
- package/dist/src/lib/generated-allowlist.js.map +1 -1
- package/dist/src/lib/generated-config-surfaces.d.ts +703 -203
- package/dist/src/lib/generated-config-surfaces.js +2270 -2014
- package/dist/src/lib/generated-config-surfaces.js.map +1 -1
- package/dist/src/lib/generated-sdk-types.d.ts +1 -1
- package/dist/src/lib/generated-sdk-types.js +1 -1
- package/dist/src/lib/generated-sdk-types.js.map +1 -1
- package/dist/src/lib/init-adopt.js +12 -3
- package/dist/src/lib/init-adopt.js.map +1 -1
- package/dist/src/lib/init-login-note.d.ts +13 -0
- package/dist/src/lib/init-login-note.js +22 -0
- package/dist/src/lib/init-login-note.js.map +1 -0
- package/dist/src/lib/json-value.d.ts +6 -0
- package/dist/src/lib/json-value.js +13 -0
- package/dist/src/lib/json-value.js.map +1 -0
- package/dist/src/lib/load-routes.d.ts +33 -0
- package/dist/src/lib/load-routes.js +35 -0
- package/dist/src/lib/load-routes.js.map +1 -0
- package/dist/src/lib/local-state.d.ts +38 -7
- package/dist/src/lib/local-state.js +172 -49
- package/dist/src/lib/local-state.js.map +1 -1
- package/dist/src/lib/local-test-cases.d.ts +5 -2
- package/dist/src/lib/local-test-cases.js +7 -4
- package/dist/src/lib/local-test-cases.js.map +1 -1
- package/dist/src/lib/log-inspection.d.ts +42 -4
- package/dist/src/lib/log-inspection.js +37 -5
- package/dist/src/lib/log-inspection.js.map +1 -1
- package/dist/src/lib/login-exchange-check.d.ts +18 -0
- package/dist/src/lib/login-exchange-check.js +27 -0
- package/dist/src/lib/login-exchange-check.js.map +1 -0
- package/dist/src/lib/login-scope-lines.d.ts +25 -0
- package/dist/src/lib/login-scope-lines.js +45 -0
- package/dist/src/lib/login-scope-lines.js.map +1 -0
- package/dist/src/lib/logout-admin-session.d.ts +2 -1
- package/dist/src/lib/logout-admin-session.js +7 -1
- package/dist/src/lib/logout-admin-session.js.map +1 -1
- package/dist/src/lib/object-status-filter.d.ts +9 -1
- package/dist/src/lib/object-status-filter.js +9 -1
- package/dist/src/lib/object-status-filter.js.map +1 -1
- package/dist/src/lib/output.js +2 -0
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/paginate.d.ts +0 -5
- package/dist/src/lib/paginate.js +0 -5
- package/dist/src/lib/paginate.js.map +1 -1
- package/dist/src/lib/pin-refusal-hint.d.ts +12 -0
- package/dist/src/lib/pin-refusal-hint.js +35 -0
- package/dist/src/lib/pin-refusal-hint.js.map +1 -0
- package/dist/src/lib/primitive-env-plugin.d.ts +99 -0
- package/dist/src/lib/primitive-env-plugin.js +408 -0
- package/dist/src/lib/primitive-env-plugin.js.map +1 -0
- package/dist/src/lib/project-config.d.ts +7 -0
- package/dist/src/lib/project-config.js +22 -1
- package/dist/src/lib/project-config.js.map +1 -1
- package/dist/src/lib/project-login-apps.d.ts +8 -0
- package/dist/src/lib/project-login-apps.js +27 -0
- package/dist/src/lib/project-login-apps.js.map +1 -0
- package/dist/src/lib/prompt-schema-codegen.d.ts +10 -3
- package/dist/src/lib/prompt-schema-codegen.js +52 -11
- package/dist/src/lib/prompt-schema-codegen.js.map +1 -1
- package/dist/src/lib/pull-write.d.ts +31 -0
- package/dist/src/lib/pull-write.js +49 -0
- package/dist/src/lib/pull-write.js.map +1 -0
- package/dist/src/lib/push-failure-report.d.ts +45 -0
- package/dist/src/lib/push-failure-report.js +57 -0
- package/dist/src/lib/push-failure-report.js.map +1 -0
- package/dist/src/lib/records-verbs.d.ts +69 -0
- package/dist/src/lib/records-verbs.js +40 -0
- package/dist/src/lib/records-verbs.js.map +1 -0
- package/dist/src/lib/refresh-admin-credentials.d.ts +7 -0
- package/dist/src/lib/refresh-admin-credentials.js +16 -4
- package/dist/src/lib/refresh-admin-credentials.js.map +1 -1
- package/dist/src/lib/root-import-decision.d.ts +65 -0
- package/dist/src/lib/root-import-decision.js +109 -0
- package/dist/src/lib/root-import-decision.js.map +1 -0
- package/dist/src/lib/run-status.d.ts +1 -1
- package/dist/src/lib/run-status.generated.d.ts +2 -2
- package/dist/src/lib/run-status.generated.js +3 -1
- package/dist/src/lib/run-status.generated.js.map +1 -1
- package/dist/src/lib/run-status.js +1 -1
- package/dist/src/lib/schema-codegen/generated-schema-descriptor.d.ts +129 -0
- package/dist/src/lib/schema-codegen/generated-schema-descriptor.js +269 -0
- package/dist/src/lib/schema-codegen/generated-schema-descriptor.js.map +1 -0
- package/dist/src/lib/schema-codegen/naming.d.ts +35 -0
- package/dist/src/lib/schema-codegen/naming.js +83 -0
- package/dist/src/lib/schema-codegen/naming.js.map +1 -0
- package/dist/src/lib/schema-codegen/schemaToTs.d.ts +82 -0
- package/dist/src/lib/schema-codegen/schemaToTs.js +305 -0
- package/dist/src/lib/schema-codegen/schemaToTs.js.map +1 -0
- package/dist/src/lib/scope-request.d.ts +84 -0
- package/dist/src/lib/scope-request.js +235 -0
- package/dist/src/lib/scope-request.js.map +1 -0
- package/dist/src/lib/session-id.d.ts +9 -0
- package/dist/src/lib/session-id.js +28 -0
- package/dist/src/lib/session-id.js.map +1 -0
- package/dist/src/lib/snapshot-build-rows.d.ts +21 -0
- package/dist/src/lib/snapshot-build-rows.js +22 -0
- package/dist/src/lib/snapshot-build-rows.js.map +1 -1
- package/dist/src/lib/snapshot-build.js +8 -3
- package/dist/src/lib/snapshot-build.js.map +1 -1
- package/dist/src/lib/snapshots.d.ts +17 -1
- package/dist/src/lib/snapshots.js +50 -2
- package/dist/src/lib/snapshots.js.map +1 -1
- package/dist/src/lib/swift-codegen/agentGenerator.d.ts +6 -2
- package/dist/src/lib/swift-codegen/agentGenerator.js +57 -5
- package/dist/src/lib/swift-codegen/agentGenerator.js.map +1 -1
- package/dist/src/lib/swift-codegen/banners.d.ts +34 -7
- package/dist/src/lib/swift-codegen/banners.js +37 -7
- package/dist/src/lib/swift-codegen/banners.js.map +1 -1
- package/dist/src/lib/swift-codegen/dbGenerator.d.ts +4 -3
- package/dist/src/lib/swift-codegen/dbGenerator.js +14 -117
- package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -1
- package/dist/src/lib/swift-codegen/functionGenerator.d.ts +3 -3
- package/dist/src/lib/swift-codegen/functionGenerator.js +23 -18
- package/dist/src/lib/swift-codegen/functionGenerator.js.map +1 -1
- package/dist/src/lib/swift-codegen/generator.d.ts +5 -5
- package/dist/src/lib/swift-codegen/generator.js +35 -40
- package/dist/src/lib/swift-codegen/generator.js.map +1 -1
- package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +3 -3
- package/dist/src/lib/swift-codegen/schemaToSwift.js +4 -4
- package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -1
- package/dist/src/lib/swift-codegen/swiftKeys.d.ts +19 -0
- package/dist/src/lib/swift-codegen/swiftKeys.js +31 -0
- package/dist/src/lib/swift-codegen/swiftKeys.js.map +1 -0
- package/dist/src/lib/sync-paths.d.ts +35 -0
- package/dist/src/lib/sync-paths.js +57 -5
- package/dist/src/lib/sync-paths.js.map +1 -1
- package/dist/src/lib/sync-resource-types.d.ts +26 -26
- package/dist/src/lib/sync-resource-types.js +61 -189
- package/dist/src/lib/sync-resource-types.js.map +1 -1
- package/dist/src/lib/sync-selectors.d.ts +5 -1
- package/dist/src/lib/sync-selectors.js +23 -13
- package/dist/src/lib/sync-selectors.js.map +1 -1
- package/dist/src/lib/test-case-file-names.d.ts +3 -5
- package/dist/src/lib/test-case-file-names.js +3 -5
- package/dist/src/lib/test-case-file-names.js.map +1 -1
- package/dist/src/lib/toml-database-config.d.ts +15 -75
- package/dist/src/lib/toml-database-config.js +17 -306
- package/dist/src/lib/toml-database-config.js.map +1 -1
- package/dist/src/lib/transient-retry.d.ts +43 -0
- package/dist/src/lib/transient-retry.js +94 -0
- package/dist/src/lib/transient-retry.js.map +1 -0
- package/dist/src/lib/vars-override.d.ts +105 -0
- package/dist/src/lib/vars-override.js +143 -0
- package/dist/src/lib/vars-override.js.map +1 -0
- package/dist/src/lib/webhook-capture.d.ts +43 -0
- package/dist/src/lib/webhook-capture.js +91 -0
- package/dist/src/lib/webhook-capture.js.map +1 -0
- package/dist/src/lib/webhook-deliver.d.ts +18 -20
- package/dist/src/lib/webhook-deliver.js +34 -89
- package/dist/src/lib/webhook-deliver.js.map +1 -1
- package/dist/src/lib/webhook-test-run.d.ts +32 -0
- package/dist/src/lib/webhook-test-run.js +108 -0
- package/dist/src/lib/webhook-test-run.js.map +1 -0
- package/dist/src/lib/workflow-codegen/generator.d.ts +7 -7
- package/dist/src/lib/workflow-codegen/generator.js +16 -23
- package/dist/src/lib/workflow-codegen/generator.js.map +1 -1
- package/dist/src/lib/workflow-codegen/invokerIR.d.ts +6 -12
- package/dist/src/lib/workflow-codegen/invokerIR.js +4 -5
- package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -1
- package/dist/src/lib/workflow-toml-validator.d.ts +2 -0
- package/dist/src/lib/workflow-toml-validator.js +28 -1
- package/dist/src/lib/workflow-toml-validator.js.map +1 -1
- package/dist/src/types/index.d.ts +30 -37
- package/dist/src/validators.d.ts +7 -55
- package/dist/src/validators.js +7 -53
- package/dist/src/validators.js.map +1 -1
- package/dist/src/vite.d.ts +17 -0
- package/dist/src/vite.js +18 -0
- package/dist/src/vite.js.map +1 -0
- package/package.json +20 -12
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Mode A (project mode): credentials live at
|
|
9
9
|
* <projectRoot>/.primitive/credentials.json
|
|
10
|
-
* with per
|
|
10
|
+
* with one login per server (#4512), keyed by normalized server URL:
|
|
11
11
|
* {
|
|
12
|
-
* "
|
|
13
|
-
* "
|
|
14
|
-
*
|
|
12
|
+
* "servers": {
|
|
13
|
+
* "https://api.example.com": {
|
|
14
|
+
* "login": { "accessToken": "...", "refreshToken": "...", ... },
|
|
15
|
+
* "leftovers": { "staging": { ... } }
|
|
16
|
+
* }
|
|
15
17
|
* }
|
|
16
18
|
* }
|
|
17
19
|
*
|
|
@@ -33,16 +35,51 @@
|
|
|
33
35
|
* project either. The last two are the divergence from the intent's "login and
|
|
34
36
|
* init and nothing else", adjudicated on #3154 as finding F1.
|
|
35
37
|
*
|
|
38
|
+
* A fifth, `apps delete`, says it pinned to a server (#4905,
|
|
39
|
+
* `allowGlobalCredentialsForServer()`): the server `PRIMITIVE_SERVER_URL`
|
|
40
|
+
* names, or production when it is unset, as `login` resolves it. The global
|
|
41
|
+
* login is used only when that server issued it, so the command still never
|
|
42
|
+
* acts against whatever server the last `login` targeted.
|
|
43
|
+
*
|
|
36
44
|
* The opt-in is per ACTION, not per argv: `primitive --env prod login` and
|
|
37
45
|
* `primitive login` reach the same line, so there is no flag shape for a
|
|
38
46
|
* pre-parse to miss. And it only PERMITS the global file — inside a project
|
|
39
|
-
* all
|
|
47
|
+
* all five still use the project's file.
|
|
48
|
+
*
|
|
49
|
+
* Every environment on a server — committed or machine-local — reads and
|
|
50
|
+
* writes that server's one login, so one login and one logout cover them all
|
|
51
|
+
* (#4512). A server's `leftovers` are logins an older CLI stored per
|
|
52
|
+
* environment: never read, revoked by `logout`.
|
|
53
|
+
*
|
|
54
|
+
* Before #4512 a login lived in a per-environment slot under `environments`,
|
|
55
|
+
* a child environment (#4511) using its parent's. Such a slot is read only
|
|
56
|
+
* while the file has no entry for its server. The first write for a server
|
|
57
|
+
* creates the entry and moves the server's legacy slots out: a mere copy of
|
|
58
|
+
* the login being written is dropped, any other becomes a leftover. From then
|
|
59
|
+
* on a cleared login stays cleared; no legacy slot is read for that server.
|
|
60
|
+
*
|
|
61
|
+
* A login records the server that issued it (#4492), and credentials from
|
|
62
|
+
* another server than the environment's are never stored. A legacy slot
|
|
63
|
+
* recording another server than the invoking environment's is never used (its
|
|
64
|
+
* tokens are not sent); a legacy slot that records no server is read against
|
|
65
|
+
* the environment's `apiUrl`.
|
|
66
|
+
*
|
|
67
|
+
* In a linked git worktree the project's file is the main worktree's
|
|
68
|
+
* (#4492, `credentials-home.ts`), so one login serves every worktree of the
|
|
69
|
+
* repository that holds the project. Every project-mode write is a locked
|
|
70
|
+
* read-modify-write with an atomic replace (`credentials-lock.ts`), because
|
|
71
|
+
* several processes — one per worktree — write that one file.
|
|
40
72
|
*/
|
|
41
73
|
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "fs";
|
|
42
74
|
import { homedir } from "os";
|
|
43
|
-
import { join } from "path";
|
|
44
|
-
import { getCurrentEnvironment, getProjectConfig } from "./env-resolver.js";
|
|
45
|
-
import { missingConfigMessage, PrimitiveEnvError } from "./env-resolver-core.js";
|
|
75
|
+
import { dirname, join } from "path";
|
|
76
|
+
import { getCurrentEnvironment, getProjectConfig, } from "./env-resolver.js";
|
|
77
|
+
import { missingConfigMessage, PROJECT_CONFIG_FILENAME, PROJECT_TREE_DIR, PrimitiveEnvError, readLocalState, } from "./env-resolver-core.js";
|
|
78
|
+
import { DEFAULT_SERVER_URL, PRODUCTION_SERVER_URL } from "./constants.js";
|
|
79
|
+
import { normalizeServerUrl } from "./channel.js";
|
|
80
|
+
import { credentialsRootFor, ignoredLocalCredentialsFile, oldGitHint, otherSharingProjectRoots, sharedWorktreeCount, } from "./credentials-home.js";
|
|
81
|
+
import { acquireCredentialsLock, fileVersion, replaceFileUnderLock } from "./credentials-lock.js";
|
|
82
|
+
import { sessionIdOfToken } from "./session-id.js";
|
|
46
83
|
export const PROJECT_LOCAL_DIR = ".primitive";
|
|
47
84
|
export const PROJECT_CREDENTIALS_FILENAME = "credentials.json";
|
|
48
85
|
/**
|
|
@@ -86,17 +123,30 @@ export function isProjectMode() {
|
|
|
86
123
|
*/
|
|
87
124
|
export function getCredentialsFilePath() {
|
|
88
125
|
const env = resolveCurrentEnvironment();
|
|
89
|
-
if (env)
|
|
90
|
-
return
|
|
91
|
-
}
|
|
126
|
+
if (env)
|
|
127
|
+
return credentialsFileForProject(env.projectRoot);
|
|
92
128
|
return getLegacyCredentialsFile();
|
|
93
129
|
}
|
|
94
130
|
/**
|
|
95
|
-
*
|
|
131
|
+
* The credentials file of the project at `projectRoot`: its own
|
|
132
|
+
* `.primitive/credentials.json`, or in a linked git worktree the main
|
|
133
|
+
* worktree's for the same project (#4492).
|
|
134
|
+
*/
|
|
135
|
+
export function credentialsFileForProject(projectRoot) {
|
|
136
|
+
return join(credentialsRootFor(projectRoot), PROJECT_LOCAL_DIR, PROJECT_CREDENTIALS_FILENAME);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Set by the actions that may read or write the global credentials file
|
|
96
140
|
* with no project in scope. Process-wide, because one invocation runs one
|
|
97
141
|
* command; never reset, because a command does not stop being `login`.
|
|
98
142
|
*/
|
|
99
143
|
let globalCredentialsAllowed = false;
|
|
144
|
+
/**
|
|
145
|
+
* The server a pinned allowance names (#4905): the global login is used only
|
|
146
|
+
* when this server issued it, and only its credentials are written. Null for
|
|
147
|
+
* the unpinned allowance of the login family.
|
|
148
|
+
*/
|
|
149
|
+
let globalCredentialsServer = null;
|
|
100
150
|
/**
|
|
101
151
|
* Declare that THIS command may fall back to `~/.primitive/credentials.json`
|
|
102
152
|
* when no project is in scope. Called at the top of `login`, `logout`, `init`
|
|
@@ -105,6 +155,17 @@ let globalCredentialsAllowed = false;
|
|
|
105
155
|
export function allowGlobalCredentials() {
|
|
106
156
|
globalCredentialsAllowed = true;
|
|
107
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Declare that THIS command may use the global credentials file when no
|
|
160
|
+
* project is in scope, but only for `serverUrl`: a stored login another server
|
|
161
|
+
* issued is refused with `server-mismatch` before any of its tokens is sent,
|
|
162
|
+
* credentials from another server are never written, and `PRIMITIVE_TOKEN` is
|
|
163
|
+
* sent to `serverUrl`. Called by `apps delete` (#4905), and nowhere else.
|
|
164
|
+
*/
|
|
165
|
+
export function allowGlobalCredentialsForServer(serverUrl) {
|
|
166
|
+
globalCredentialsAllowed = true;
|
|
167
|
+
globalCredentialsServer = trimSlash(serverUrl);
|
|
168
|
+
}
|
|
108
169
|
/**
|
|
109
170
|
* Drop the declaration again. One invocation runs one command, so nothing in
|
|
110
171
|
* the CLI calls this — it exists so a test file can exercise both sides of the
|
|
@@ -112,6 +173,25 @@ export function allowGlobalCredentials() {
|
|
|
112
173
|
*/
|
|
113
174
|
export function resetGlobalCredentialsAllowance() {
|
|
114
175
|
globalCredentialsAllowed = false;
|
|
176
|
+
globalCredentialsServer = null;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The login command for the global login of `serverUrl`, up to its `--app`
|
|
180
|
+
* value: prefixed with `PRIMITIVE_SERVER_URL=<server>` unless the server is
|
|
181
|
+
* production, which `login` targets when the variable is unset.
|
|
182
|
+
*/
|
|
183
|
+
export function globalLoginCommand(serverUrl) {
|
|
184
|
+
const prefix = sameServer(serverUrl, PRODUCTION_SERVER_URL) ? "" : `PRIMITIVE_SERVER_URL=${serverUrl} `;
|
|
185
|
+
return `${prefix}primitive login --app`;
|
|
186
|
+
}
|
|
187
|
+
/** The refusal of a global login another server than the pinned one issued. */
|
|
188
|
+
function globalReadMismatch(stored, pinned) {
|
|
189
|
+
const issuer = stored ?? "a server it does not record";
|
|
190
|
+
const target = stored ? ` To target ${stored}, set PRIMITIVE_SERVER_URL=${stored}.` : "";
|
|
191
|
+
return new PrimitiveEnvError("server-mismatch", `The login in ${getLegacyCredentialsFile()} was issued by ${issuer}, but this command targets ` +
|
|
192
|
+
`${pinned} (PRIMITIVE_SERVER_URL, or production when it is unset); its tokens were not sent.` +
|
|
193
|
+
`${target} To sign in to ${pinned}, run '${globalLoginCommand(pinned)} <app-id>' (or --app all); ` +
|
|
194
|
+
`it replaces the stored login.`);
|
|
115
195
|
}
|
|
116
196
|
/**
|
|
117
197
|
* Resolves the current environment. If an environment CAN'T be resolved
|
|
@@ -169,6 +249,7 @@ function legacyCredentialsRecord(credentials) {
|
|
|
169
249
|
role: credentials.role,
|
|
170
250
|
name: credentials.name,
|
|
171
251
|
globalAdminAppId: credentials.globalAdminAppId,
|
|
252
|
+
stepUp: credentials.stepUp,
|
|
172
253
|
};
|
|
173
254
|
}
|
|
174
255
|
/**
|
|
@@ -176,28 +257,318 @@ function legacyCredentialsRecord(credentials) {
|
|
|
176
257
|
* an empty shape when the file is missing or unreadable.
|
|
177
258
|
*/
|
|
178
259
|
function loadProjectCredentialsFile(projectRoot) {
|
|
179
|
-
const path =
|
|
260
|
+
const path = credentialsFileForProject(projectRoot);
|
|
180
261
|
const data = readJsonFile(path);
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
262
|
+
const isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
263
|
+
if (!isRecord(data))
|
|
264
|
+
return { environments: {}, servers: {} };
|
|
265
|
+
const file = {
|
|
266
|
+
environments: isRecord(data.environments) ? data.environments : {},
|
|
267
|
+
servers: isRecord(data.servers) ? data.servers : {},
|
|
268
|
+
};
|
|
184
269
|
// EVERY slot, not just the one about to be written: each write reserializes
|
|
185
270
|
// the whole file, so a stale key anywhere would survive forever.
|
|
186
|
-
for (const slot of Object.values(
|
|
271
|
+
for (const slot of Object.values(file.environments)) {
|
|
187
272
|
stripRetiredCurrentApp(slot);
|
|
188
273
|
}
|
|
189
|
-
|
|
274
|
+
for (const entry of Object.values(file.servers)) {
|
|
275
|
+
stripRetiredCurrentApp(entry?.login);
|
|
276
|
+
for (const slot of Object.values(entry?.leftovers ?? {}))
|
|
277
|
+
stripRetiredCurrentApp(slot);
|
|
278
|
+
}
|
|
279
|
+
return file;
|
|
190
280
|
}
|
|
191
|
-
|
|
192
|
-
|
|
281
|
+
/**
|
|
282
|
+
* The one way a project's credentials file is written: take the lock, read
|
|
283
|
+
* the file, apply `mutate`, and — when it reports a change — replace the file
|
|
284
|
+
* atomically while still holding the lock. Throws when the lock stays held by
|
|
285
|
+
* another writer, or is lost or overtaken by another writer's commit before
|
|
286
|
+
* this one; the file is then unchanged.
|
|
287
|
+
*/
|
|
288
|
+
function updateProjectCredentialsFile(projectRoot, mutate) {
|
|
289
|
+
const path = credentialsFileForProject(projectRoot);
|
|
290
|
+
const dir = dirname(path);
|
|
193
291
|
ensureDir(dir);
|
|
194
|
-
const
|
|
195
|
-
|
|
292
|
+
const lock = acquireCredentialsLock(dir);
|
|
293
|
+
try {
|
|
294
|
+
const readVersion = fileVersion(path);
|
|
295
|
+
const file = loadProjectCredentialsFile(projectRoot);
|
|
296
|
+
if (mutate(file))
|
|
297
|
+
replaceFileUnderLock(lock, path, JSON.stringify(file, null, 2), readVersion);
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
lock.release();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const trimSlash = (url) => url.replace(/\/+$/, "");
|
|
304
|
+
/**
|
|
305
|
+
* Whether two server URLs name the same server: scheme and host compare
|
|
306
|
+
* case-insensitively and the path with trailing slashes removed. A URL that
|
|
307
|
+
* does not parse compares as its trailing-slash-trimmed string.
|
|
308
|
+
*/
|
|
309
|
+
export function sameServer(a, b) {
|
|
310
|
+
try {
|
|
311
|
+
const x = new URL(a);
|
|
312
|
+
const y = new URL(b);
|
|
313
|
+
return (x.protocol.toLowerCase() === y.protocol.toLowerCase() &&
|
|
314
|
+
x.host.toLowerCase() === y.host.toLowerCase() &&
|
|
315
|
+
trimSlash(x.pathname) === trimSlash(y.pathname));
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return trimSlash(a) === trimSlash(b);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/** The refusal of credentials issued by another server than the slot's environment's. */
|
|
322
|
+
function writeMismatch(envName, incoming, configured) {
|
|
323
|
+
return new PrimitiveEnvError("server-mismatch", `These credentials were issued by ${incoming}, but the "${envName}" login is for ${configured}; ` +
|
|
324
|
+
`nothing was stored. Run 'primitive -e ${envName} login' to sign in to ${configured}, or run ` +
|
|
325
|
+
`this command outside a project or from an environment that points at ${incoming}.`);
|
|
326
|
+
}
|
|
327
|
+
/** Whether a stored slot records a server other than `apiUrl`. */
|
|
328
|
+
function recordsOtherServer(stored, apiUrl) {
|
|
329
|
+
return stored.serverUrl !== undefined && !sameServer(stored.serverUrl, apiUrl);
|
|
330
|
+
}
|
|
331
|
+
/** The slot record for `credentials`, recording the server that issued them. */
|
|
332
|
+
function storedRecord(credentials) {
|
|
333
|
+
return {
|
|
334
|
+
serverUrl: credentials.serverUrl,
|
|
335
|
+
accessToken: credentials.accessToken,
|
|
336
|
+
refreshToken: credentials.refreshToken,
|
|
337
|
+
expiresAt: credentials.expiresAt,
|
|
338
|
+
adminId: credentials.adminId,
|
|
339
|
+
email: credentials.email,
|
|
340
|
+
role: credentials.role,
|
|
341
|
+
name: credentials.name,
|
|
342
|
+
globalAdminAppId: credentials.globalAdminAppId,
|
|
343
|
+
stepUp: credentials.stepUp,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* The slot holding `env`'s login (#4511). A child environment uses its
|
|
348
|
+
* parent's slot, and the children of a slot are the machine-local entries of
|
|
349
|
+
* its tree on its server. A machine-local entry on another server is not a
|
|
350
|
+
* child: a login for one server says nothing about another, so it keeps a slot
|
|
351
|
+
* of its own.
|
|
352
|
+
*/
|
|
353
|
+
function loginSlotOf(env) {
|
|
354
|
+
const own = { slot: env.name, children: [], serverUrl: env.config.apiUrl };
|
|
355
|
+
const project = getProjectConfig();
|
|
356
|
+
const parent = project?.environments[env.tree];
|
|
357
|
+
if (!project || !parent)
|
|
358
|
+
return own;
|
|
359
|
+
const onParentServer = (apiUrl) => sameServer(apiUrl, parent.apiUrl);
|
|
360
|
+
if (env.local && !onParentServer(env.config.apiUrl))
|
|
361
|
+
return own;
|
|
362
|
+
const children = Object.entries(readLocalState(env.projectConfigPath, project).environments)
|
|
363
|
+
.filter(([, entry]) => entry.tree === env.tree && onParentServer(entry.apiUrl))
|
|
364
|
+
.map(([name]) => name);
|
|
365
|
+
return { slot: env.tree, children, serverUrl: parent.apiUrl };
|
|
366
|
+
}
|
|
367
|
+
/** Whether two stored logins hold the same server session. */
|
|
368
|
+
function sameSession(a, b) {
|
|
369
|
+
const sid = sessionIdOfToken(a.accessToken);
|
|
370
|
+
if (sid !== undefined)
|
|
371
|
+
return sid === sessionIdOfToken(b.accessToken);
|
|
372
|
+
return Boolean(a.accessToken) && a.accessToken === b.accessToken;
|
|
373
|
+
}
|
|
374
|
+
/** The expiry (`exp`, in seconds) a JWT carries, or undefined. */
|
|
375
|
+
function expiryOfToken(token) {
|
|
376
|
+
if (!token)
|
|
377
|
+
return undefined;
|
|
196
378
|
try {
|
|
197
|
-
|
|
379
|
+
const payload = JSON.parse(Buffer.from(token.split(".")[1] ?? "", "base64url").toString("utf-8"));
|
|
380
|
+
return typeof payload?.exp === "number" ? payload.exp : undefined;
|
|
198
381
|
}
|
|
199
382
|
catch {
|
|
200
|
-
|
|
383
|
+
return undefined;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Whether `slot`'s refresh token may outlive `login`'s: a copy refreshed in
|
|
388
|
+
* the child since it was made. It can still revoke the session after the
|
|
389
|
+
* parent's token has expired, so it is not thrown away unrevoked. Only an
|
|
390
|
+
* identical token, or one known to expire no later, is said not to.
|
|
391
|
+
*/
|
|
392
|
+
function outlivesRefresh(slot, login) {
|
|
393
|
+
if (slot.refreshToken === login.refreshToken)
|
|
394
|
+
return false;
|
|
395
|
+
const mine = expiryOfToken(slot.refreshToken);
|
|
396
|
+
const theirs = expiryOfToken(login.refreshToken);
|
|
397
|
+
return mine === undefined || theirs === undefined || mine > theirs;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Whether `slot` holds a live step-up `login` does not: one a `login --scope
|
|
401
|
+
* admin` in the child stored beside the copied login. A step-up is a session
|
|
402
|
+
* of its own, which revoking the login's does not end.
|
|
403
|
+
*/
|
|
404
|
+
function ownStepUp(slot, login) {
|
|
405
|
+
const stepUp = slot.stepUp;
|
|
406
|
+
if (!stepUp || new Date(stepUp.expiresAt).getTime() <= Date.now())
|
|
407
|
+
return false;
|
|
408
|
+
const sessionOf = (s) => s.sessionId || sessionIdOfToken(s.accessToken) || s.accessToken;
|
|
409
|
+
return !login.stepUp || sessionOf(stepUp) !== sessionOf(login.stepUp);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* The child slots that are mere copies of `login`: written when a child was
|
|
413
|
+
* registered before children used their parent's login, holding its session
|
|
414
|
+
* and nothing a logout could still need. A child slot holding another
|
|
415
|
+
* session, a step-up of its own, or a refresh token that may outlive the
|
|
416
|
+
* parent's is not one: it stays (it is never read) until `logout` revokes it.
|
|
417
|
+
*/
|
|
418
|
+
function copiedChildSlots(file, login, children) {
|
|
419
|
+
return children.filter((name) => {
|
|
420
|
+
const slot = file.environments[name];
|
|
421
|
+
return (slot !== undefined &&
|
|
422
|
+
sameSession(slot, login) &&
|
|
423
|
+
!outlivesRefresh(slot, login) &&
|
|
424
|
+
!ownStepUp(slot, login));
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Removes the {@link copiedChildSlots} of the login in `slot`, deciding again
|
|
429
|
+
* under the lock against the file as it is then. Best-effort, so a read never
|
|
430
|
+
* fails on a file it cannot rewrite or a lock it cannot take.
|
|
431
|
+
*/
|
|
432
|
+
function dropCopiedChildSlots(projectRoot, file, slot, children) {
|
|
433
|
+
const login = file.environments[slot];
|
|
434
|
+
if (!login || copiedChildSlots(file, login, children).length === 0)
|
|
435
|
+
return;
|
|
436
|
+
try {
|
|
437
|
+
updateProjectCredentialsFile(projectRoot, (current) => {
|
|
438
|
+
const currentLogin = current.environments[slot];
|
|
439
|
+
if (!currentLogin)
|
|
440
|
+
return false;
|
|
441
|
+
const copies = copiedChildSlots(current, currentLogin, children);
|
|
442
|
+
for (const name of copies)
|
|
443
|
+
delete current.environments[name];
|
|
444
|
+
return copies.length > 0;
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
// The copies are ignored either way; the next read tries again.
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* The key of `apiUrl`'s login in `servers` (#4512): scheme, host and port,
|
|
453
|
+
* and the path without trailing slashes. A URL that does not parse keys as
|
|
454
|
+
* its trailing-slash-trimmed string.
|
|
455
|
+
*/
|
|
456
|
+
function serverKey(apiUrl) {
|
|
457
|
+
return normalizeServerUrl(apiUrl) ?? trimSlash(apiUrl);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* The server of every environment of the project whose config is at
|
|
461
|
+
* `configPath`, committed and machine-local, by name in config order. Null
|
|
462
|
+
* when the config cannot be read, so a caller never takes "no environment"
|
|
463
|
+
* for an answer it did not get.
|
|
464
|
+
*/
|
|
465
|
+
function environmentServers(configPath) {
|
|
466
|
+
const config = readJsonFile(configPath);
|
|
467
|
+
if (!config?.environments || typeof config.environments !== "object")
|
|
468
|
+
return null;
|
|
469
|
+
const servers = {};
|
|
470
|
+
for (const [name, entry] of Object.entries(config.environments)) {
|
|
471
|
+
if (typeof entry?.apiUrl === "string")
|
|
472
|
+
servers[name] = entry.apiUrl;
|
|
473
|
+
}
|
|
474
|
+
try {
|
|
475
|
+
for (const [name, entry] of Object.entries(readLocalState(configPath).environments)) {
|
|
476
|
+
if (!(name in servers))
|
|
477
|
+
servers[name] = entry.apiUrl;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
// An unreadable local state names no machine-local environment.
|
|
482
|
+
}
|
|
483
|
+
return servers;
|
|
484
|
+
}
|
|
485
|
+
/** The project config of a project root, where `init` and `env remove` find it. */
|
|
486
|
+
function configPathOf(projectRoot) {
|
|
487
|
+
return join(projectRoot, PROJECT_TREE_DIR, PROJECT_CONFIG_FILENAME);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* The legacy slots of `file` that belong to `server`: one recording that
|
|
491
|
+
* server, and one named after an environment on it, whatever it records. The
|
|
492
|
+
* latter is the slot that environment read; one recording another server was
|
|
493
|
+
* never used there, and keeps its server so `logout` revokes it where it was
|
|
494
|
+
* issued.
|
|
495
|
+
*/
|
|
496
|
+
function legacySlotsOn(file, server, servers) {
|
|
497
|
+
const on = (apiUrl) => apiUrl !== undefined && serverKey(apiUrl) === server;
|
|
498
|
+
return Object.entries(file.environments)
|
|
499
|
+
.filter(([name, slot]) => on(slot.serverUrl) || on(servers?.[name]))
|
|
500
|
+
.map(([name]) => name);
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Create `servers[server]` for the login about to be written, moving the
|
|
504
|
+
* server's legacy slots out of `environments`. A slot holding `login`'s
|
|
505
|
+
* session that nothing a logout needs is dropped (an identical or older
|
|
506
|
+
* refresh token, no live step-up of its own, #4511's rules); any other slot
|
|
507
|
+
* becomes a leftover. No-op once the server has an entry.
|
|
508
|
+
*/
|
|
509
|
+
function migrateServer(file, server, login, servers) {
|
|
510
|
+
const existing = file.servers[server];
|
|
511
|
+
if (existing)
|
|
512
|
+
return existing;
|
|
513
|
+
const leftovers = {};
|
|
514
|
+
for (const name of legacySlotsOn(file, server, servers)) {
|
|
515
|
+
const slot = file.environments[name];
|
|
516
|
+
const copy = sameSession(slot, login) && !outlivesRefresh(slot, login) && !ownStepUp(slot, login);
|
|
517
|
+
if (!copy)
|
|
518
|
+
leftovers[name] = slot;
|
|
519
|
+
delete file.environments[name];
|
|
520
|
+
}
|
|
521
|
+
const entry = Object.keys(leftovers).length > 0 ? { leftovers } : {};
|
|
522
|
+
file.servers[server] = entry;
|
|
523
|
+
return entry;
|
|
524
|
+
}
|
|
525
|
+
/** The variable a CI job puts its admin token in (#3890). */
|
|
526
|
+
export const PRIMITIVE_TOKEN_VAR = "PRIMITIVE_TOKEN";
|
|
527
|
+
/**
|
|
528
|
+
* The partition header the live test harness needs when no credentials file
|
|
529
|
+
* names one. Deployed servers ignore the header for admin identity.
|
|
530
|
+
*/
|
|
531
|
+
const PARTITION_VAR = "PRIMITIVE_GLOBAL_ADMIN_APP_ID";
|
|
532
|
+
/**
|
|
533
|
+
* The partition a login exchanges in when it builds on no stored login: the
|
|
534
|
+
* one `PRIMITIVE_GLOBAL_ADMIN_APP_ID` names, which only the live test harness
|
|
535
|
+
* sets. Deployed servers ignore the header for admin identity.
|
|
536
|
+
*/
|
|
537
|
+
export function partitionFromEnvironment() {
|
|
538
|
+
return process.env[PARTITION_VAR]?.trim() || undefined;
|
|
539
|
+
}
|
|
540
|
+
/** The token in `PRIMITIVE_TOKEN`, or null when it is unset or blank. */
|
|
541
|
+
export function environmentToken() {
|
|
542
|
+
const value = process.env[PRIMITIVE_TOKEN_VAR]?.trim();
|
|
543
|
+
return value ? value : null;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Credentials made from `PRIMITIVE_TOKEN` for `serverUrl` (#3890): the token
|
|
547
|
+
* verbatim (an opaque CI token or a JWT), no refresh token, an expiry far
|
|
548
|
+
* enough away that no refresh is ever attempted, and no identity, which the
|
|
549
|
+
* server answers for. Null when the variable is unset or blank.
|
|
550
|
+
*/
|
|
551
|
+
function environmentCredentials(serverUrl) {
|
|
552
|
+
const token = environmentToken();
|
|
553
|
+
if (!token)
|
|
554
|
+
return null;
|
|
555
|
+
const partition = process.env[PARTITION_VAR]?.trim();
|
|
556
|
+
return {
|
|
557
|
+
source: "env",
|
|
558
|
+
serverUrl,
|
|
559
|
+
accessToken: token,
|
|
560
|
+
refreshToken: "",
|
|
561
|
+
expiresAt: "9999-12-31T23:59:59.000Z",
|
|
562
|
+
adminId: "",
|
|
563
|
+
email: "",
|
|
564
|
+
role: "admin",
|
|
565
|
+
...(partition ? { globalAdminAppId: partition } : {}),
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
/** Refuse to write credentials `PRIMITIVE_TOKEN` supplied. */
|
|
569
|
+
function assertStorable(credentials) {
|
|
570
|
+
if (credentials.source === "env") {
|
|
571
|
+
throw new Error("Credentials from PRIMITIVE_TOKEN are never written to disk.");
|
|
201
572
|
}
|
|
202
573
|
}
|
|
203
574
|
/**
|
|
@@ -205,21 +576,72 @@ function writeProjectCredentialsFile(projectRoot, file) {
|
|
|
205
576
|
* legacy global file. Returns the standard Credentials shape so existing
|
|
206
577
|
* callers don't need to change.
|
|
207
578
|
*
|
|
208
|
-
* In project mode, we blend the
|
|
209
|
-
*
|
|
210
|
-
* the
|
|
579
|
+
* In project mode, we blend the server's stored login with the serverUrl
|
|
580
|
+
* from primitive/config.json to produce the Credentials the rest of the CLI
|
|
581
|
+
* expects (#4512). Before the server's first write a legacy slot is read
|
|
582
|
+
* instead: the environment's own, or its parent's for a child environment
|
|
583
|
+
* (#4511), and any child slot found to be a copy of that login is removed.
|
|
584
|
+
*
|
|
585
|
+
* `PRIMITIVE_TOKEN` (#3890), when set, takes precedence over any stored
|
|
586
|
+
* login and no file is read. It does not loosen the outside-a-project
|
|
587
|
+
* refusal: the environment is resolved first, exactly as for a stored login.
|
|
588
|
+
*
|
|
589
|
+
* Outside a project under a server pin (#4905), the global login is returned
|
|
590
|
+
* only when the pinned server issued it, and `PRIMITIVE_TOKEN` is for the
|
|
591
|
+
* pinned server.
|
|
211
592
|
*/
|
|
212
593
|
export function loadCredentialsStore() {
|
|
213
594
|
const env = resolveCurrentEnvironment();
|
|
595
|
+
const fromEnvironment = environmentCredentials(env ? env.config.apiUrl : globalCredentialsServer ?? DEFAULT_SERVER_URL.replace(/\/$/, ""));
|
|
596
|
+
if (fromEnvironment)
|
|
597
|
+
return fromEnvironment;
|
|
214
598
|
if (!env) {
|
|
215
|
-
|
|
599
|
+
const stored = loadLegacyCredentials();
|
|
600
|
+
if (stored &&
|
|
601
|
+
globalCredentialsServer !== null &&
|
|
602
|
+
(!stored.serverUrl || !sameServer(stored.serverUrl, globalCredentialsServer))) {
|
|
603
|
+
throw globalReadMismatch(stored.serverUrl, globalCredentialsServer);
|
|
604
|
+
}
|
|
605
|
+
return stored;
|
|
216
606
|
}
|
|
217
607
|
const file = loadProjectCredentialsFile(env.projectRoot);
|
|
218
|
-
const
|
|
608
|
+
const entry = file.servers[serverKey(env.config.apiUrl)];
|
|
609
|
+
if (entry)
|
|
610
|
+
return entry.login ? storedCredentials(env.config.apiUrl, entry.login) : null;
|
|
611
|
+
const { slot, children } = loginSlotOf(env);
|
|
612
|
+
const stored = file.environments[slot];
|
|
219
613
|
if (!stored)
|
|
220
614
|
return null;
|
|
615
|
+
if (recordsOtherServer(stored, env.config.apiUrl)) {
|
|
616
|
+
throw new PrimitiveEnvError("server-mismatch", `The "${env.name}" login in ${getCredentialsFilePath()} was issued by ${stored.serverUrl}, ` +
|
|
617
|
+
`but "${env.name}" now points at ${env.config.apiUrl}; its tokens were not sent. ` +
|
|
618
|
+
`Run 'primitive -e ${env.name} login' to sign in there.`);
|
|
619
|
+
}
|
|
620
|
+
dropCopiedChildSlots(env.projectRoot, file, slot, children);
|
|
621
|
+
return storedCredentials(env.config.apiUrl, stored);
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* The current environment's legacy login when its slot records another server
|
|
625
|
+
* than the environment's, blended with the server it RECORDS; null otherwise,
|
|
626
|
+
* and always once the environment's server has a login entry (#4512). For
|
|
627
|
+
* `logout` alone, which revokes such a login at the server that issued it and
|
|
628
|
+
* so never sends its tokens anywhere else.
|
|
629
|
+
*/
|
|
630
|
+
export function mismatchedLogin() {
|
|
631
|
+
const env = resolveCurrentEnvironment();
|
|
632
|
+
if (!env || environmentToken())
|
|
633
|
+
return null;
|
|
634
|
+
const file = loadProjectCredentialsFile(env.projectRoot);
|
|
635
|
+
if (file.servers[serverKey(env.config.apiUrl)])
|
|
636
|
+
return null;
|
|
637
|
+
const stored = file.environments[loginSlotOf(env).slot];
|
|
638
|
+
if (!stored || !recordsOtherServer(stored, env.config.apiUrl))
|
|
639
|
+
return null;
|
|
640
|
+
return storedCredentials(stored.serverUrl, stored);
|
|
641
|
+
}
|
|
642
|
+
function storedCredentials(serverUrl, stored) {
|
|
221
643
|
return {
|
|
222
|
-
serverUrl
|
|
644
|
+
serverUrl,
|
|
223
645
|
accessToken: stored.accessToken,
|
|
224
646
|
refreshToken: stored.refreshToken,
|
|
225
647
|
expiresAt: stored.expiresAt,
|
|
@@ -228,18 +650,114 @@ export function loadCredentialsStore() {
|
|
|
228
650
|
role: stored.role,
|
|
229
651
|
name: stored.name,
|
|
230
652
|
globalAdminAppId: stored.globalAdminAppId,
|
|
653
|
+
...(stored.stepUp ? { stepUp: stored.stepUp } : {}),
|
|
231
654
|
};
|
|
232
655
|
}
|
|
233
656
|
/**
|
|
234
|
-
*
|
|
235
|
-
*
|
|
657
|
+
* The environments that share the current environment's login (#4512): every
|
|
658
|
+
* environment of the project on its server, committed and machine-local, in
|
|
659
|
+
* config order; and the committed environment whose tree the current one
|
|
660
|
+
* uses, which names the login. Null outside a project, where there is one
|
|
661
|
+
* global login.
|
|
662
|
+
*/
|
|
663
|
+
export function loginEnvironments() {
|
|
664
|
+
const env = resolveCurrentEnvironment();
|
|
665
|
+
if (!env)
|
|
666
|
+
return null;
|
|
667
|
+
const server = serverKey(env.config.apiUrl);
|
|
668
|
+
const servers = environmentServers(env.projectConfigPath) ?? {};
|
|
669
|
+
const environments = Object.entries(servers)
|
|
670
|
+
.filter(([, apiUrl]) => serverKey(apiUrl) === server)
|
|
671
|
+
.map(([name]) => name);
|
|
672
|
+
return { tree: env.tree, environments: environments.length > 0 ? environments : [env.name] };
|
|
673
|
+
}
|
|
674
|
+
/** The current project's root, or null outside a project or when it cannot be resolved. */
|
|
675
|
+
function currentProjectRoot() {
|
|
676
|
+
try {
|
|
677
|
+
return getCurrentEnvironment()?.projectRoot ?? null;
|
|
678
|
+
}
|
|
679
|
+
catch {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* What a login, logout or not-logged-in refusal adds about a linked git
|
|
685
|
+
* worktree (#4492). Each is null when it has nothing to say: outside a
|
|
686
|
+
* project, and where the project's login is its own.
|
|
687
|
+
*/
|
|
688
|
+
export const worktreeLoginNotes = {
|
|
689
|
+
/** After a login stored in another worktree's file: where it went. */
|
|
690
|
+
storedElsewhere() {
|
|
691
|
+
const root = currentProjectRoot();
|
|
692
|
+
if (!root || credentialsRootFor(root) === root)
|
|
693
|
+
return null;
|
|
694
|
+
return `Stored in ${credentialsFileForProject(root)}, shared with every git worktree of this repository that holds this project.`;
|
|
695
|
+
},
|
|
696
|
+
/** After a logout of `slot`'s login, when more than one worktree uses it. */
|
|
697
|
+
sharedLogout(slot) {
|
|
698
|
+
const root = currentProjectRoot();
|
|
699
|
+
if (!root || sharedWorktreeCount(root) < 2)
|
|
700
|
+
return null;
|
|
701
|
+
return `The ${slot} login is shared by every git worktree of this repository that holds this project, so all of them are signed out.`;
|
|
702
|
+
},
|
|
703
|
+
/** A linked worktree's own credentials file, which is never read. */
|
|
704
|
+
ignoredFile() {
|
|
705
|
+
const root = currentProjectRoot();
|
|
706
|
+
const ignored = root ? ignoredLocalCredentialsFile(root) : null;
|
|
707
|
+
if (!root || !ignored)
|
|
708
|
+
return null;
|
|
709
|
+
return `Ignored ${ignored}: this worktree uses the shared login in ${credentialsFileForProject(root)}. You can delete the ignored file.`;
|
|
710
|
+
},
|
|
711
|
+
/** After "not logged in", when git is why a linked worktree has no login. */
|
|
712
|
+
notLoggedIn() {
|
|
713
|
+
const root = currentProjectRoot();
|
|
714
|
+
return root ? oldGitHint(root) : null;
|
|
715
|
+
},
|
|
716
|
+
};
|
|
717
|
+
/**
|
|
718
|
+
* The logins on the current environment's server that no command reads: the
|
|
719
|
+
* server's leftovers (#4512), or before its first write the legacy slots on
|
|
720
|
+
* it other than the one the environment reads — sessions signed in to another
|
|
721
|
+
* environment, and copies that kept a step-up or a refresh token of their own
|
|
722
|
+
* (#4511). `logout` revokes them before {@link clearLeftoverLogins} removes
|
|
723
|
+
* them. Empty outside a project.
|
|
724
|
+
*/
|
|
725
|
+
export function leftoverLogins() {
|
|
726
|
+
const env = resolveCurrentEnvironment();
|
|
727
|
+
if (!env)
|
|
728
|
+
return [];
|
|
729
|
+
const file = loadProjectCredentialsFile(env.projectRoot);
|
|
730
|
+
const server = serverKey(env.config.apiUrl);
|
|
731
|
+
const entry = file.servers[server];
|
|
732
|
+
const stored = entry
|
|
733
|
+
? Object.values(entry.leftovers ?? {})
|
|
734
|
+
: legacyLeftovers(file, env).map((name) => file.environments[name]);
|
|
735
|
+
return stored.map((slot) => storedCredentials(slot.serverUrl ?? env.config.apiUrl, slot));
|
|
736
|
+
}
|
|
737
|
+
/** The legacy slots on `env`'s server other than the one it reads. */
|
|
738
|
+
function legacyLeftovers(file, env) {
|
|
739
|
+
const own = loginSlotOf(env).slot;
|
|
740
|
+
return legacySlotsOn(file, serverKey(env.config.apiUrl), environmentServers(env.projectConfigPath)).filter((name) => name !== own);
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Saves credentials. In project mode, writes the login of the environment's
|
|
744
|
+
* server (#4512), migrating the server's legacy slots at its first write. In
|
|
745
|
+
* legacy mode, writes the global file.
|
|
236
746
|
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
747
|
+
* In project mode the login records the incoming `serverUrl`, which must be
|
|
748
|
+
* the same server as the environment's `apiUrl`: credentials from another
|
|
749
|
+
* server are refused with `server-mismatch` and nothing is written (#4492).
|
|
750
|
+
* In legacy mode under a server pin (#4905), so are credentials from another
|
|
751
|
+
* server than the pinned one.
|
|
239
752
|
*/
|
|
240
753
|
export function saveCredentialsStore(credentials) {
|
|
754
|
+
assertStorable(credentials);
|
|
241
755
|
const env = resolveCurrentEnvironment();
|
|
242
756
|
if (!env) {
|
|
757
|
+
if (globalCredentialsServer !== null && !sameServer(credentials.serverUrl, globalCredentialsServer)) {
|
|
758
|
+
throw new PrimitiveEnvError("server-mismatch", `These credentials were issued by ${credentials.serverUrl}, but this command targets ` +
|
|
759
|
+
`${globalCredentialsServer}; nothing was stored.`);
|
|
760
|
+
}
|
|
243
761
|
ensureDir(getLegacyConfigDir());
|
|
244
762
|
writeFileSync(getLegacyCredentialsFile(), JSON.stringify(legacyCredentialsRecord(credentials), null, 2), { mode: 0o600 });
|
|
245
763
|
try {
|
|
@@ -250,22 +768,29 @@ export function saveCredentialsStore(credentials) {
|
|
|
250
768
|
}
|
|
251
769
|
return;
|
|
252
770
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
771
|
+
if (!sameServer(credentials.serverUrl, env.config.apiUrl)) {
|
|
772
|
+
throw writeMismatch(env.name, credentials.serverUrl, env.config.apiUrl);
|
|
773
|
+
}
|
|
774
|
+
writeServerLogin(env.projectRoot, environmentServers(env.projectConfigPath), credentials);
|
|
775
|
+
}
|
|
776
|
+
/** Write `credentials` as their server's login in the project at `projectRoot`. */
|
|
777
|
+
function writeServerLogin(projectRoot, servers, credentials) {
|
|
778
|
+
const record = storedRecord(credentials);
|
|
779
|
+
const server = serverKey(credentials.serverUrl);
|
|
780
|
+
updateProjectCredentialsFile(projectRoot, (file) => {
|
|
781
|
+
migrateServer(file, server, record, servers).login = record;
|
|
782
|
+
return true;
|
|
783
|
+
});
|
|
265
784
|
}
|
|
266
785
|
/**
|
|
267
786
|
* Clears credentials for the current environment (project mode) or
|
|
268
|
-
* deletes the legacy file (legacy mode).
|
|
787
|
+
* deletes the legacy file (legacy mode). In project mode that is the login of
|
|
788
|
+
* the environment's server; its entry and leftovers stay, so a legacy slot is
|
|
789
|
+
* never read for the server again (#4512). Before the server's first write it
|
|
790
|
+
* is the legacy slot the environment reads, and the child slots holding only
|
|
791
|
+
* that login's session (#4511), which its caller has just revoked. A slot
|
|
792
|
+
* holding another session or a step-up of its own stays for `logout` to
|
|
793
|
+
* revoke ({@link clearLeftoverLogins}).
|
|
269
794
|
*/
|
|
270
795
|
export function clearCredentialsStore() {
|
|
271
796
|
const env = resolveCurrentEnvironment();
|
|
@@ -280,57 +805,167 @@ export function clearCredentialsStore() {
|
|
|
280
805
|
}
|
|
281
806
|
return;
|
|
282
807
|
}
|
|
808
|
+
if (!existsSync(credentialsFileForProject(env.projectRoot)))
|
|
809
|
+
return;
|
|
810
|
+
const server = serverKey(env.config.apiUrl);
|
|
811
|
+
const { slot, children } = loginSlotOf(env);
|
|
812
|
+
updateProjectCredentialsFile(env.projectRoot, (file) => {
|
|
813
|
+
const entry = file.servers[server];
|
|
814
|
+
if (entry) {
|
|
815
|
+
if (!entry.login)
|
|
816
|
+
return false;
|
|
817
|
+
delete entry.login;
|
|
818
|
+
return true;
|
|
819
|
+
}
|
|
820
|
+
const login = file.environments[slot];
|
|
821
|
+
if (!login)
|
|
822
|
+
return false;
|
|
823
|
+
for (const name of children) {
|
|
824
|
+
const child = file.environments[name];
|
|
825
|
+
if (child && sameSession(child, login) && !ownStepUp(child, login)) {
|
|
826
|
+
delete file.environments[name];
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
delete file.environments[slot];
|
|
830
|
+
return true;
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Removes the logins {@link leftoverLogins} returned, once `logout` has
|
|
835
|
+
* revoked them: the server's leftovers, or before its first write the other
|
|
836
|
+
* legacy slots on it. No-op outside a project.
|
|
837
|
+
*/
|
|
838
|
+
export function clearLeftoverLogins() {
|
|
839
|
+
const env = resolveCurrentEnvironment();
|
|
840
|
+
if (!env)
|
|
841
|
+
return;
|
|
842
|
+
const server = serverKey(env.config.apiUrl);
|
|
283
843
|
const file = loadProjectCredentialsFile(env.projectRoot);
|
|
284
|
-
|
|
285
|
-
|
|
844
|
+
const entry = file.servers[server];
|
|
845
|
+
if (entry ? !entry.leftovers : legacyLeftovers(file, env).length === 0)
|
|
846
|
+
return;
|
|
847
|
+
updateProjectCredentialsFile(env.projectRoot, (current) => {
|
|
848
|
+
const currentEntry = current.servers[server];
|
|
849
|
+
if (currentEntry) {
|
|
850
|
+
if (!currentEntry.leftovers)
|
|
851
|
+
return false;
|
|
852
|
+
delete currentEntry.leftovers;
|
|
853
|
+
return true;
|
|
854
|
+
}
|
|
855
|
+
const names = legacyLeftovers(current, env);
|
|
856
|
+
for (const name of names)
|
|
857
|
+
delete current.environments[name];
|
|
858
|
+
return names.length > 0;
|
|
859
|
+
});
|
|
286
860
|
}
|
|
287
861
|
/**
|
|
288
|
-
* Removes
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
* name
|
|
862
|
+
* Removes what a removed environment leaves in a specific project's
|
|
863
|
+
* credentials file, so stale tokens from a previously-removed environment can
|
|
864
|
+
* never be silently re-used if the same name is later added back: a legacy
|
|
865
|
+
* slot or leftover of its name, and the login of the removed environment's
|
|
866
|
+
* server `removedApiUrl` when no remaining environment uses it (#4512). No
|
|
867
|
+
* other server's entry is touched: the file is shared by every worktree
|
|
868
|
+
* (#4492), and a server this worktree does not use may be another worktree's
|
|
869
|
+
* machine-local environment's. For the same reason the removed server's
|
|
870
|
+
* login stays while an environment of any worktree sharing the file uses it,
|
|
871
|
+
* or while that cannot be told. Used by `env remove`, after the environment
|
|
872
|
+
* left the config at `configPath`. Returns whether anything was removed.
|
|
873
|
+
* Never throws.
|
|
292
874
|
*/
|
|
293
|
-
export function clearCredentialsForEnvInProject(projectRoot, envName) {
|
|
875
|
+
export function clearCredentialsForEnvInProject(projectRoot, envName, configPath = configPathOf(projectRoot), removedApiUrl) {
|
|
294
876
|
try {
|
|
295
|
-
const
|
|
296
|
-
if (
|
|
297
|
-
|
|
298
|
-
|
|
877
|
+
const removed = removeCredentialsSlotInProject(projectRoot, envName);
|
|
878
|
+
if (removedApiUrl === undefined)
|
|
879
|
+
return removed;
|
|
880
|
+
const server = serverKey(removedApiUrl);
|
|
881
|
+
const others = otherSharingProjectRoots(projectRoot);
|
|
882
|
+
if (!others)
|
|
883
|
+
return removed;
|
|
884
|
+
for (const path of [configPath, ...others.map(configPathOf)]) {
|
|
885
|
+
const servers = environmentServers(path);
|
|
886
|
+
if (!servers)
|
|
887
|
+
return removed;
|
|
888
|
+
if (Object.values(servers).some((apiUrl) => serverKey(apiUrl) === server))
|
|
889
|
+
return removed;
|
|
299
890
|
}
|
|
891
|
+
if (!loadProjectCredentialsFile(projectRoot).servers[server])
|
|
892
|
+
return removed;
|
|
893
|
+
updateProjectCredentialsFile(projectRoot, (file) => {
|
|
894
|
+
if (!file.servers[server])
|
|
895
|
+
return false;
|
|
896
|
+
delete file.servers[server];
|
|
897
|
+
return true;
|
|
898
|
+
});
|
|
899
|
+
return true;
|
|
300
900
|
}
|
|
301
901
|
catch {
|
|
302
902
|
// Best-effort — removal is not a hard requirement for env removal to succeed.
|
|
903
|
+
return false;
|
|
303
904
|
}
|
|
304
905
|
}
|
|
906
|
+
/** Whether `file` holds a legacy slot or a leftover named `envName`. */
|
|
907
|
+
function holdsSlotOf(file, envName) {
|
|
908
|
+
return (file.environments[envName] !== undefined ||
|
|
909
|
+
Object.values(file.servers).some((entry) => entry.leftovers?.[envName] !== undefined));
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Removes the legacy slot and the leftovers named `envName` in a specific
|
|
913
|
+
* project's credentials file; a server's login, which every environment on
|
|
914
|
+
* the server uses, is never one of them (#4512). Returns false when there was
|
|
915
|
+
* none; throws when there was one and the file could not be rewritten, so a
|
|
916
|
+
* caller that must not lose track of a login can tell the two apart.
|
|
917
|
+
*/
|
|
918
|
+
export function removeCredentialsSlotInProject(projectRoot, envName) {
|
|
919
|
+
if (!holdsSlotOf(loadProjectCredentialsFile(projectRoot), envName))
|
|
920
|
+
return false;
|
|
921
|
+
let removed = false;
|
|
922
|
+
updateProjectCredentialsFile(projectRoot, (file) => {
|
|
923
|
+
removed = holdsSlotOf(file, envName);
|
|
924
|
+
delete file.environments[envName];
|
|
925
|
+
for (const entry of Object.values(file.servers)) {
|
|
926
|
+
if (!entry.leftovers?.[envName])
|
|
927
|
+
continue;
|
|
928
|
+
delete entry.leftovers[envName];
|
|
929
|
+
if (Object.keys(entry.leftovers).length === 0)
|
|
930
|
+
delete entry.leftovers;
|
|
931
|
+
}
|
|
932
|
+
return removed;
|
|
933
|
+
});
|
|
934
|
+
return removed;
|
|
935
|
+
}
|
|
305
936
|
/**
|
|
306
|
-
* Writes
|
|
307
|
-
*
|
|
308
|
-
*
|
|
937
|
+
* Writes `credentials` as their server's login in a specific project's
|
|
938
|
+
* credentials file (#4512). Used by `primitive init` to carry the session it
|
|
939
|
+
* just authenticated into the freshly scaffolded project, so the first
|
|
309
940
|
* in-project CLI command works without a second `primitive login`.
|
|
310
941
|
*/
|
|
311
|
-
export function
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
accessToken: credentials.accessToken,
|
|
315
|
-
refreshToken: credentials.refreshToken,
|
|
316
|
-
expiresAt: credentials.expiresAt,
|
|
317
|
-
adminId: credentials.adminId,
|
|
318
|
-
email: credentials.email,
|
|
319
|
-
role: credentials.role,
|
|
320
|
-
name: credentials.name,
|
|
321
|
-
globalAdminAppId: credentials.globalAdminAppId,
|
|
322
|
-
};
|
|
323
|
-
writeProjectCredentialsFile(projectRoot, file);
|
|
942
|
+
export function seedCredentialsForServerInProject(projectRoot, credentials) {
|
|
943
|
+
assertStorable(credentials);
|
|
944
|
+
writeServerLogin(projectRoot, environmentServers(configPathOf(projectRoot)), credentials);
|
|
324
945
|
}
|
|
325
946
|
/**
|
|
326
|
-
* Lists
|
|
327
|
-
*
|
|
947
|
+
* Lists the environments of the project that have a login to use: every
|
|
948
|
+
* environment on a server holding a login, and before a server's first write
|
|
949
|
+
* the environments with a legacy slot of their own. Returns an empty array in
|
|
950
|
+
* legacy mode.
|
|
328
951
|
*/
|
|
329
952
|
export function listAuthenticatedEnvironments() {
|
|
330
953
|
const env = resolveCurrentEnvironment();
|
|
331
954
|
if (!env)
|
|
332
955
|
return [];
|
|
333
956
|
const file = loadProjectCredentialsFile(env.projectRoot);
|
|
334
|
-
|
|
957
|
+
const servers = environmentServers(env.projectConfigPath) ?? {};
|
|
958
|
+
const names = new Set();
|
|
959
|
+
for (const [name, apiUrl] of Object.entries(servers)) {
|
|
960
|
+
const entry = file.servers[serverKey(apiUrl)];
|
|
961
|
+
if (entry ? entry.login : file.environments[name])
|
|
962
|
+
names.add(name);
|
|
963
|
+
}
|
|
964
|
+
for (const name of Object.keys(file.environments)) {
|
|
965
|
+
const apiUrl = servers[name];
|
|
966
|
+
if (apiUrl === undefined || !file.servers[serverKey(apiUrl)])
|
|
967
|
+
names.add(name);
|
|
968
|
+
}
|
|
969
|
+
return [...names];
|
|
335
970
|
}
|
|
336
971
|
//# sourceMappingURL=credentials-store.js.map
|