primitive-admin 1.1.0-alpha.7 → 1.1.0-alpha.71
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 +404 -80
- package/assets/skill/skills/primitive-platform/SKILL.md +808 -0
- package/dist/bin/primitive.d.ts +2 -0
- package/dist/bin/primitive.js +294 -21
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/admins.d.ts +2 -0
- package/dist/src/commands/admins.js +138 -19
- package/dist/src/commands/admins.js.map +1 -1
- package/dist/src/commands/analytics.d.ts +2 -0
- package/dist/src/commands/analytics.js +544 -55
- package/dist/src/commands/analytics.js.map +1 -1
- package/dist/src/commands/apps.d.ts +2 -0
- package/dist/src/commands/apps.js +51 -96
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/auth.d.ts +2 -0
- package/dist/src/commands/auth.js +177 -7
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/blob-buckets.d.ts +2 -0
- package/dist/src/commands/blob-buckets.js +330 -0
- package/dist/src/commands/blob-buckets.js.map +1 -0
- package/dist/src/commands/catalog.d.ts +2 -0
- package/dist/src/commands/catalog.js +37 -38
- package/dist/src/commands/catalog.js.map +1 -1
- package/dist/src/commands/collection-type-configs.d.ts +2 -0
- package/dist/src/commands/collection-type-configs.js +92 -0
- package/dist/src/commands/collection-type-configs.js.map +1 -0
- package/dist/src/commands/collections.d.ts +2 -0
- package/dist/src/commands/collections.js +565 -0
- package/dist/src/commands/collections.js.map +1 -0
- package/dist/src/commands/comparisons.d.ts +2 -0
- package/dist/src/commands/comparisons.js +6 -6
- package/dist/src/commands/comparisons.js.map +1 -1
- package/dist/src/commands/config.d.ts +46 -0
- package/dist/src/commands/config.js +479 -0
- package/dist/src/commands/config.js.map +1 -0
- package/dist/src/commands/connections.d.ts +2 -0
- package/dist/src/commands/connections.js +100 -0
- package/dist/src/commands/connections.js.map +1 -0
- package/dist/src/commands/cron-triggers.d.ts +2 -0
- package/dist/src/commands/cron-triggers.js +265 -0
- package/dist/src/commands/cron-triggers.js.map +1 -0
- package/dist/src/commands/database-type-configs.d.ts +2 -0
- package/dist/src/commands/database-type-configs.js +171 -0
- package/dist/src/commands/database-type-configs.js.map +1 -0
- package/dist/src/commands/database-types.d.ts +2 -0
- package/dist/src/commands/database-types.js +471 -0
- package/dist/src/commands/database-types.js.map +1 -0
- package/dist/src/commands/databases.d.ts +65 -0
- package/dist/src/commands/databases.js +2140 -112
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.d.ts +2 -0
- package/dist/src/commands/documents.js +1357 -19
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/email-templates.d.ts +2 -0
- package/dist/src/commands/email-templates.js +174 -0
- package/dist/src/commands/email-templates.js.map +1 -0
- package/dist/src/commands/env.d.ts +23 -0
- package/dist/src/commands/env.js +333 -0
- package/dist/src/commands/env.js.map +1 -0
- package/dist/src/commands/feature-flags.d.ts +14 -0
- package/dist/src/commands/feature-flags.js +116 -0
- package/dist/src/commands/feature-flags.js.map +1 -0
- package/dist/src/commands/group-type-configs.d.ts +2 -0
- package/dist/src/commands/group-type-configs.js +86 -0
- package/dist/src/commands/group-type-configs.js.map +1 -0
- package/dist/src/commands/groups.d.ts +2 -0
- package/dist/src/commands/groups.js +38 -99
- package/dist/src/commands/groups.js.map +1 -1
- package/dist/src/commands/guides.d.ts +223 -0
- package/dist/src/commands/guides.js +617 -65
- package/dist/src/commands/guides.js.map +1 -1
- package/dist/src/commands/init.d.ts +25 -0
- package/dist/src/commands/init.js +1605 -208
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.d.ts +2 -0
- package/dist/src/commands/integrations.js +380 -178
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/llm.d.ts +2 -0
- package/dist/src/commands/llm.js +4 -2
- package/dist/src/commands/llm.js.map +1 -1
- package/dist/src/commands/locks.d.ts +8 -0
- package/dist/src/commands/locks.js +160 -0
- package/dist/src/commands/locks.js.map +1 -0
- package/dist/src/commands/metadata-category-configs.d.ts +12 -0
- package/dist/src/commands/metadata-category-configs.js +112 -0
- package/dist/src/commands/metadata-category-configs.js.map +1 -0
- package/dist/src/commands/metadata.d.ts +2 -0
- package/dist/src/commands/metadata.js +281 -0
- package/dist/src/commands/metadata.js.map +1 -0
- package/dist/src/commands/prompts.d.ts +2 -0
- package/dist/src/commands/prompts.js +225 -584
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/rule-sets.d.ts +3 -0
- package/dist/src/commands/rule-sets.js +272 -0
- package/dist/src/commands/rule-sets.js.map +1 -0
- package/dist/src/commands/scripts.d.ts +20 -0
- package/dist/src/commands/scripts.js +554 -0
- package/dist/src/commands/scripts.js.map +1 -0
- package/dist/src/commands/secrets.d.ts +2 -0
- package/dist/src/commands/secrets.js +108 -0
- package/dist/src/commands/secrets.js.map +1 -0
- package/dist/src/commands/sessions.d.ts +2 -0
- package/dist/src/commands/sessions.js +75 -0
- package/dist/src/commands/sessions.js.map +1 -0
- package/dist/src/commands/skill.d.ts +2 -0
- package/dist/src/commands/skill.js +29 -0
- package/dist/src/commands/skill.js.map +1 -0
- package/dist/src/commands/sync-app-settings.d.ts +158 -0
- package/dist/src/commands/sync-app-settings.js +330 -0
- package/dist/src/commands/sync-app-settings.js.map +1 -0
- package/dist/src/commands/sync.d.ts +2323 -0
- package/dist/src/commands/sync.js +15065 -843
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.d.ts +2 -0
- package/dist/src/commands/tokens.js +130 -21
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/users.d.ts +2 -0
- package/dist/src/commands/users.js +532 -23
- package/dist/src/commands/users.js.map +1 -1
- package/dist/src/commands/vars.d.ts +8 -0
- package/dist/src/commands/vars.js +96 -0
- package/dist/src/commands/vars.js.map +1 -0
- package/dist/src/commands/waitlist.d.ts +2 -0
- package/dist/src/commands/waitlist.js +10 -10
- package/dist/src/commands/waitlist.js.map +1 -1
- package/dist/src/commands/webhooks.d.ts +2 -0
- package/dist/src/commands/webhooks.js +562 -0
- package/dist/src/commands/webhooks.js.map +1 -0
- package/dist/src/commands/workflows.d.ts +116 -0
- package/dist/src/commands/workflows.js +1583 -681
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/access-rule-display.d.ts +21 -0
- package/dist/src/lib/access-rule-display.js +34 -0
- package/dist/src/lib/access-rule-display.js.map +1 -0
- package/dist/src/lib/api-client.d.ts +1936 -0
- package/dist/src/lib/api-client.js +1826 -138
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/app-settings-descriptor.d.ts +263 -0
- package/dist/src/lib/app-settings-descriptor.js +575 -0
- package/dist/src/lib/app-settings-descriptor.js.map +1 -0
- package/dist/src/lib/auth-flow.d.ts +8 -0
- package/dist/src/lib/batch.d.ts +26 -0
- package/dist/src/lib/batch.js +32 -0
- package/dist/src/lib/batch.js.map +1 -0
- package/dist/src/lib/block-layout.d.ts +160 -0
- package/dist/src/lib/block-layout.js +451 -0
- package/dist/src/lib/block-layout.js.map +1 -0
- package/dist/src/lib/canonical-json.d.ts +12 -0
- package/dist/src/lib/canonical-json.js +35 -0
- package/dist/src/lib/canonical-json.js.map +1 -0
- package/dist/src/lib/channel.d.ts +30 -0
- package/dist/src/lib/channel.js +68 -0
- package/dist/src/lib/channel.js.map +1 -0
- package/dist/src/lib/cli-manifest.d.ts +68 -0
- package/dist/src/lib/cli-manifest.js +71 -0
- package/dist/src/lib/cli-manifest.js.map +1 -0
- package/dist/src/lib/codegen-shared/generatedFiles.d.ts +101 -0
- package/dist/src/lib/codegen-shared/generatedFiles.js +191 -0
- package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -0
- package/dist/src/lib/codegen-shared/prettierStable.d.ts +262 -0
- package/dist/src/lib/codegen-shared/prettierStable.js +610 -0
- package/dist/src/lib/codegen-shared/prettierStable.js.map +1 -0
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +68 -0
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +168 -0
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -0
- package/dist/src/lib/config-object-descriptor.d.ts +127 -0
- package/dist/src/lib/config-object-descriptor.js +658 -0
- package/dist/src/lib/config-object-descriptor.js.map +1 -0
- package/dist/src/lib/config-payload.d.ts +85 -0
- package/dist/src/lib/config-payload.js +116 -0
- package/dist/src/lib/config-payload.js.map +1 -0
- package/dist/src/lib/config-surface.d.ts +130 -0
- package/dist/src/lib/config-surface.js +300 -0
- package/dist/src/lib/config-surface.js.map +1 -0
- package/dist/src/lib/config-toml.d.ts +10 -0
- package/dist/src/lib/config-toml.js +42 -0
- package/dist/src/lib/config-toml.js.map +1 -0
- package/dist/src/lib/config.d.ts +53 -0
- package/dist/src/lib/config.js +92 -53
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/confirm-prompt.d.ts +83 -0
- package/dist/src/lib/confirm-prompt.js +110 -0
- package/dist/src/lib/confirm-prompt.js.map +1 -0
- package/dist/src/lib/constants.d.ts +11 -0
- package/dist/src/lib/constants.js +12 -0
- package/dist/src/lib/constants.js.map +1 -0
- package/dist/src/lib/crash-handlers.d.ts +20 -0
- package/dist/src/lib/crash-handlers.js +49 -0
- package/dist/src/lib/crash-handlers.js.map +1 -0
- package/dist/src/lib/credentials-store.d.ts +89 -0
- package/dist/src/lib/credentials-store.js +330 -0
- package/dist/src/lib/credentials-store.js.map +1 -0
- package/dist/src/lib/csv.d.ts +47 -0
- package/dist/src/lib/csv.js +172 -0
- package/dist/src/lib/csv.js.map +1 -0
- package/dist/src/lib/data-input.d.ts +23 -0
- package/dist/src/lib/data-input.js +50 -0
- package/dist/src/lib/data-input.js.map +1 -0
- package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
- package/dist/src/lib/db-codegen/dbFingerprint.js +17 -0
- package/dist/src/lib/db-codegen/dbFingerprint.js.map +1 -0
- package/dist/src/lib/db-codegen/dbGenerator.d.ts +67 -0
- package/dist/src/lib/db-codegen/dbGenerator.js +170 -0
- package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -0
- package/dist/src/lib/db-codegen/dbNaming.d.ts +87 -0
- package/dist/src/lib/db-codegen/dbNaming.js +180 -0
- package/dist/src/lib/db-codegen/dbNaming.js.map +1 -0
- package/dist/src/lib/db-codegen/dbTemplates.d.ts +272 -0
- package/dist/src/lib/db-codegen/dbTemplates.js +480 -0
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -0
- package/dist/src/lib/db-codegen/dbTsTypes.d.ts +73 -0
- package/dist/src/lib/db-codegen/dbTsTypes.js +139 -0
- package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -0
- package/dist/src/lib/db-codegen/dbTypeIR.d.ts +146 -0
- package/dist/src/lib/db-codegen/dbTypeIR.js +517 -0
- package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -0
- package/dist/src/lib/db-codegen/generated-operation-def-descriptor.d.ts +112 -0
- package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js +211 -0
- package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js.map +1 -0
- package/dist/src/lib/env-resolver-core.d.ts +147 -0
- package/dist/src/lib/env-resolver-core.js +265 -0
- package/dist/src/lib/env-resolver-core.js.map +1 -0
- package/dist/src/lib/env-resolver.d.ts +84 -0
- package/dist/src/lib/env-resolver.js +133 -0
- package/dist/src/lib/env-resolver.js.map +1 -0
- package/dist/src/lib/fetch.d.ts +5 -0
- package/dist/src/lib/generated-allowlist.d.ts +28 -0
- package/dist/src/lib/generated-allowlist.js +277 -0
- package/dist/src/lib/generated-allowlist.js.map +1 -0
- package/dist/src/lib/generated-config-surfaces.d.ts +682 -0
- package/dist/src/lib/generated-config-surfaces.js +4058 -0
- package/dist/src/lib/generated-config-surfaces.js.map +1 -0
- package/dist/src/lib/generated-template-lint.d.ts +212 -0
- package/dist/src/lib/generated-template-lint.js +624 -0
- package/dist/src/lib/generated-template-lint.js.map +1 -0
- package/dist/src/lib/init-adopt.d.ts +16 -0
- package/dist/src/lib/init-adopt.js +34 -0
- package/dist/src/lib/init-adopt.js.map +1 -0
- package/dist/src/lib/init-assets.d.ts +39 -0
- package/dist/src/lib/init-assets.js +97 -0
- package/dist/src/lib/init-assets.js.map +1 -0
- package/dist/src/lib/init-config.d.ts +98 -0
- package/dist/src/lib/init-config.js +186 -0
- package/dist/src/lib/init-config.js.map +1 -0
- package/dist/src/lib/init-ios-links.d.ts +50 -0
- package/dist/src/lib/init-ios-links.js +153 -0
- package/dist/src/lib/init-ios-links.js.map +1 -0
- package/dist/src/lib/init-plan.d.ts +80 -0
- package/dist/src/lib/init-plan.js +95 -0
- package/dist/src/lib/init-plan.js.map +1 -0
- package/dist/src/lib/init-production-env.d.ts +48 -0
- package/dist/src/lib/init-production-env.js +59 -0
- package/dist/src/lib/init-production-env.js.map +1 -0
- package/dist/src/lib/init-schema.d.ts +74 -0
- package/dist/src/lib/init-schema.js +358 -0
- package/dist/src/lib/init-schema.js.map +1 -0
- package/dist/src/lib/init-xcode.d.ts +33 -0
- package/dist/src/lib/init-xcode.js +114 -0
- package/dist/src/lib/init-xcode.js.map +1 -0
- package/dist/src/lib/integration-request-config.d.ts +30 -0
- package/dist/src/lib/integration-request-config.js +145 -0
- package/dist/src/lib/integration-request-config.js.map +1 -0
- package/dist/src/lib/local-state.d.ts +55 -0
- package/dist/src/lib/local-state.js +167 -0
- package/dist/src/lib/local-state.js.map +1 -0
- package/dist/src/lib/log-inspection.d.ts +568 -0
- package/dist/src/lib/log-inspection.js +639 -0
- package/dist/src/lib/log-inspection.js.map +1 -0
- package/dist/src/lib/migration-nag.d.ts +49 -0
- package/dist/src/lib/migration-nag.js +163 -0
- package/dist/src/lib/migration-nag.js.map +1 -0
- package/dist/src/lib/object-status-filter.d.ts +22 -0
- package/dist/src/lib/object-status-filter.js +45 -0
- package/dist/src/lib/object-status-filter.js.map +1 -0
- package/dist/src/lib/output.d.ts +109 -0
- package/dist/src/lib/output.js +191 -8
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/package-manager.d.ts +140 -0
- package/dist/src/lib/package-manager.js +305 -0
- package/dist/src/lib/package-manager.js.map +1 -0
- package/dist/src/lib/paginate.d.ts +83 -0
- package/dist/src/lib/paginate.js +95 -0
- package/dist/src/lib/paginate.js.map +1 -0
- package/dist/src/lib/platform-owned.d.ts +63 -0
- package/dist/src/lib/platform-owned.js +85 -0
- package/dist/src/lib/platform-owned.js.map +1 -0
- package/dist/src/lib/project-config.d.ts +97 -0
- package/dist/src/lib/project-config.js +217 -0
- package/dist/src/lib/project-config.js.map +1 -0
- package/dist/src/lib/query-operators.d.ts +43 -0
- package/dist/src/lib/query-operators.js +80 -0
- package/dist/src/lib/query-operators.js.map +1 -0
- package/dist/src/lib/record-filter.d.ts +18 -0
- package/dist/src/lib/record-filter.js +55 -0
- package/dist/src/lib/record-filter.js.map +1 -0
- package/dist/src/lib/refresh-admin-credentials.d.ts +65 -0
- package/dist/src/lib/refresh-admin-credentials.js +103 -0
- package/dist/src/lib/refresh-admin-credentials.js.map +1 -0
- package/dist/src/lib/resolve-init-dev-port.d.ts +56 -0
- package/dist/src/lib/resolve-init-dev-port.js +55 -0
- package/dist/src/lib/resolve-init-dev-port.js.map +1 -0
- package/dist/src/lib/resolve-init-server.d.ts +64 -0
- package/dist/src/lib/resolve-init-server.js +77 -0
- package/dist/src/lib/resolve-init-server.js.map +1 -0
- package/dist/src/lib/resolve-platform.d.ts +74 -0
- package/dist/src/lib/resolve-platform.js +105 -0
- package/dist/src/lib/resolve-platform.js.map +1 -0
- package/dist/src/lib/run-status.d.ts +19 -0
- package/dist/src/lib/run-status.generated.d.ts +39 -0
- package/dist/src/lib/run-status.generated.js +66 -0
- package/dist/src/lib/run-status.generated.js.map +1 -0
- package/dist/src/lib/run-status.js +19 -0
- package/dist/src/lib/run-status.js.map +1 -0
- package/dist/src/lib/server-text-normalization.d.ts +51 -0
- package/dist/src/lib/server-text-normalization.js +90 -0
- package/dist/src/lib/server-text-normalization.js.map +1 -0
- package/dist/src/lib/server-url.d.ts +22 -0
- package/dist/src/lib/server-url.js +33 -0
- package/dist/src/lib/server-url.js.map +1 -0
- package/dist/src/lib/signing-secret-status.d.ts +81 -0
- package/dist/src/lib/signing-secret-status.js +116 -0
- package/dist/src/lib/signing-secret-status.js.map +1 -0
- package/dist/src/lib/skill-installer.d.ts +25 -0
- package/dist/src/lib/skill-installer.js +266 -0
- package/dist/src/lib/skill-installer.js.map +1 -0
- package/dist/src/lib/snapshots.d.ts +99 -0
- package/dist/src/lib/snapshots.js +357 -0
- package/dist/src/lib/snapshots.js.map +1 -0
- package/dist/src/lib/swift-codegen/dbGenerator.d.ts +113 -0
- package/dist/src/lib/swift-codegen/dbGenerator.js +914 -0
- package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -0
- package/dist/src/lib/swift-codegen/dbSwiftTypes.d.ts +42 -0
- package/dist/src/lib/swift-codegen/dbSwiftTypes.js +100 -0
- package/dist/src/lib/swift-codegen/dbSwiftTypes.js.map +1 -0
- package/dist/src/lib/swift-codegen/generator.d.ts +94 -0
- package/dist/src/lib/swift-codegen/generator.js +440 -0
- package/dist/src/lib/swift-codegen/generator.js.map +1 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +72 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.js +644 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -0
- package/dist/src/lib/swift-codegen/siblingSymbols.d.ts +94 -0
- package/dist/src/lib/swift-codegen/siblingSymbols.js +155 -0
- package/dist/src/lib/swift-codegen/siblingSymbols.js.map +1 -0
- package/dist/src/lib/swift-codegen/swiftNaming.d.ts +85 -0
- package/dist/src/lib/swift-codegen/swiftNaming.js +198 -0
- package/dist/src/lib/swift-codegen/swiftNaming.js.map +1 -0
- package/dist/src/lib/sync-dir-selector.d.ts +21 -0
- package/dist/src/lib/sync-dir-selector.js +30 -0
- package/dist/src/lib/sync-dir-selector.js.map +1 -0
- package/dist/src/lib/sync-paths.d.ts +111 -0
- package/dist/src/lib/sync-paths.js +198 -0
- package/dist/src/lib/sync-paths.js.map +1 -0
- package/dist/src/lib/sync-resource-types.d.ts +544 -0
- package/dist/src/lib/sync-resource-types.js +975 -0
- package/dist/src/lib/sync-resource-types.js.map +1 -0
- package/dist/src/lib/sync-selectors.d.ts +95 -0
- package/dist/src/lib/sync-selectors.js +228 -0
- package/dist/src/lib/sync-selectors.js.map +1 -0
- package/dist/src/lib/template.d.ts +170 -0
- package/dist/src/lib/template.js +484 -68
- package/dist/src/lib/template.js.map +1 -1
- package/dist/src/lib/test-case-keys.d.ts +29 -0
- package/dist/src/lib/test-case-keys.js +55 -0
- package/dist/src/lib/test-case-keys.js.map +1 -0
- package/dist/src/lib/test-case-variables.d.ts +15 -0
- package/dist/src/lib/test-case-variables.js +29 -0
- package/dist/src/lib/test-case-variables.js.map +1 -0
- package/dist/src/lib/token-inject.d.ts +56 -0
- package/dist/src/lib/token-inject.js +204 -0
- package/dist/src/lib/token-inject.js.map +1 -0
- package/dist/src/lib/toml-database-config.d.ts +123 -0
- package/dist/src/lib/toml-database-config.js +527 -0
- package/dist/src/lib/toml-database-config.js.map +1 -0
- package/dist/src/lib/toml-metadata-config.d.ts +151 -0
- package/dist/src/lib/toml-metadata-config.js +476 -0
- package/dist/src/lib/toml-metadata-config.js.map +1 -0
- package/dist/src/lib/toml-native-form.d.ts +46 -0
- package/dist/src/lib/toml-native-form.js +78 -0
- package/dist/src/lib/toml-native-form.js.map +1 -0
- package/dist/src/lib/toml-params-validator.d.ts +129 -0
- package/dist/src/lib/toml-params-validator.js +298 -0
- package/dist/src/lib/toml-params-validator.js.map +1 -0
- package/dist/src/lib/toml-scalar-edit.d.ts +43 -0
- package/dist/src/lib/toml-scalar-edit.js +283 -0
- package/dist/src/lib/toml-scalar-edit.js.map +1 -0
- package/dist/src/lib/user-selector.d.ts +24 -0
- package/dist/src/lib/user-selector.js +33 -0
- package/dist/src/lib/user-selector.js.map +1 -0
- package/dist/src/lib/version-check.d.ts +35 -0
- package/dist/src/lib/version-check.js +241 -0
- package/dist/src/lib/version-check.js.map +1 -0
- package/dist/src/lib/watch.d.ts +121 -0
- package/dist/src/lib/watch.js +169 -0
- package/dist/src/lib/watch.js.map +1 -0
- package/dist/src/lib/workflow-apply.d.ts +110 -0
- package/dist/src/lib/workflow-apply.js +164 -0
- package/dist/src/lib/workflow-apply.js.map +1 -0
- package/dist/src/lib/workflow-codegen/generated-schema-descriptor.d.ts +129 -0
- package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js +269 -0
- package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js.map +1 -0
- package/dist/src/lib/workflow-codegen/generator.d.ts +96 -0
- package/dist/src/lib/workflow-codegen/generator.js +361 -0
- package/dist/src/lib/workflow-codegen/generator.js.map +1 -0
- package/dist/src/lib/workflow-codegen/invokerIR.d.ts +94 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js +76 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -0
- package/dist/src/lib/workflow-codegen/naming.d.ts +33 -0
- package/dist/src/lib/workflow-codegen/naming.js +81 -0
- package/dist/src/lib/workflow-codegen/naming.js.map +1 -0
- package/dist/src/lib/workflow-codegen/schemaToTs.d.ts +80 -0
- package/dist/src/lib/workflow-codegen/schemaToTs.js +303 -0
- package/dist/src/lib/workflow-codegen/schemaToTs.js.map +1 -0
- package/dist/src/lib/workflow-config-apply.d.ts +70 -0
- package/dist/src/lib/workflow-config-apply.js +137 -0
- package/dist/src/lib/workflow-config-apply.js.map +1 -0
- package/dist/src/lib/workflow-config-sidecar.d.ts +63 -0
- package/dist/src/lib/workflow-config-sidecar.js +96 -0
- package/dist/src/lib/workflow-config-sidecar.js.map +1 -0
- package/dist/src/lib/workflow-defaults.d.ts +29 -0
- package/dist/src/lib/workflow-defaults.js +41 -0
- package/dist/src/lib/workflow-defaults.js.map +1 -0
- package/dist/src/lib/workflow-fragments.d.ts +64 -0
- package/dist/src/lib/workflow-fragments.js +342 -0
- package/dist/src/lib/workflow-fragments.js.map +1 -0
- package/dist/src/lib/workflow-include-preserve.d.ts +76 -0
- package/dist/src/lib/workflow-include-preserve.js +286 -0
- package/dist/src/lib/workflow-include-preserve.js.map +1 -0
- package/dist/src/lib/workflow-payload.d.ts +98 -0
- package/dist/src/lib/workflow-payload.js +178 -0
- package/dist/src/lib/workflow-payload.js.map +1 -0
- package/dist/src/lib/workflow-toml-validator.d.ts +202 -0
- package/dist/src/lib/workflow-toml-validator.js +757 -0
- package/dist/src/lib/workflow-toml-validator.js.map +1 -0
- package/dist/src/types/index.d.ts +581 -0
- package/dist/src/validators.d.ts +65 -0
- package/dist/src/validators.js +64 -0
- package/dist/src/validators.js.map +1 -0
- package/package.json +32 -8
|
@@ -2,13 +2,236 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import { join, basename } from "path";
|
|
4
4
|
import { error, info, warn, formatTable, json, keyValue, } from "../lib/output.js";
|
|
5
|
+
import { getServerUrl } from "../lib/config.js";
|
|
6
|
+
import { resolveChannelBranch } from "../lib/channel.js";
|
|
5
7
|
const GUIDES_CACHE_DIR = process.env.PRIMITIVE_CONFIG_DIR
|
|
6
8
|
? join(process.env.PRIMITIVE_CONFIG_DIR, "guides")
|
|
7
9
|
: join(homedir(), ".primitive", "guides");
|
|
8
10
|
const CACHE_META_FILE = join(GUIDES_CACHE_DIR, "cache-meta.json");
|
|
9
|
-
|
|
11
|
+
// The public publish-only site repo. Guides are pushed there (alongside the
|
|
12
|
+
// built docs site) by primitive-docs/scripts/publish-gh-pages.mjs; the docs
|
|
13
|
+
// SOURCE lives in the js-bao-wss monorepo, which is private and therefore
|
|
14
|
+
// can't serve raw URLs to end users.
|
|
15
|
+
const GITHUB_RAW_ROOT = "https://raw.githubusercontent.com/Primitive-Labs/primitive-docs-site";
|
|
16
|
+
const DEFAULT_DOCS_BRANCH = "main";
|
|
10
17
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
11
18
|
const CLIENT_PACKAGE_NAME = "js-bao-wss-client";
|
|
19
|
+
// Channel resolution now lives in lib/channel.ts, shared with the template
|
|
20
|
+
// download in `primitive init` (issue #1934). Re-exported here because the
|
|
21
|
+
// public shapes originated in this module (issue #1463).
|
|
22
|
+
export { normalizeServerUrl, isValidBranchName } from "../lib/channel.js";
|
|
23
|
+
/**
|
|
24
|
+
* Resolve which branch of the published site repo to fetch guides from.
|
|
25
|
+
*
|
|
26
|
+
* Branches of primitive-docs-site are named after the deploy environment
|
|
27
|
+
* whose docs they carry (published as part of each deploy — see
|
|
28
|
+
* primitive-docs/scripts/publish-gh-pages.mjs): `main` = production,
|
|
29
|
+
* `alpha` = the alpha environment. Precedence (originally issue #1463,
|
|
30
|
+
* repointed at consolidation): the undocumented `PRIMITIVE_GUIDES_BRANCH`
|
|
31
|
+
* override (validated as a plausible git ref), then alpha server-URL
|
|
32
|
+
* detection, then `main`. See `resolveChannelBranch` for the shared logic.
|
|
33
|
+
*/
|
|
34
|
+
export function resolveDocsBranch(serverUrl, override = process.env.PRIMITIVE_GUIDES_BRANCH) {
|
|
35
|
+
return resolveChannelBranch(serverUrl, override, "PRIMITIVE_GUIDES_BRANCH");
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the docs branch for the current invocation, reading the target from
|
|
39
|
+
* `getServerUrl()`. Logged-out / legacy-no-creds makes `getServerUrl()` throw;
|
|
40
|
+
* we treat that as "no target" and fall back to `main` (the override still
|
|
41
|
+
* applies). Only the branch decision is wrapped here — an invalid override
|
|
42
|
+
* still throws out to the command's error handler.
|
|
43
|
+
*/
|
|
44
|
+
function resolveDocsBranchForInvocation() {
|
|
45
|
+
let serverUrl;
|
|
46
|
+
try {
|
|
47
|
+
serverUrl = getServerUrl();
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
serverUrl = undefined;
|
|
51
|
+
}
|
|
52
|
+
return resolveDocsBranch(serverUrl);
|
|
53
|
+
}
|
|
54
|
+
// Language aliases — map the agent's natural vocabulary onto canonical values.
|
|
55
|
+
const LANGUAGE_ALIASES = {
|
|
56
|
+
typescript: "ts",
|
|
57
|
+
javascript: "ts",
|
|
58
|
+
js: "ts",
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Normalize a `--language` flag value: lowercase, trim, and map known aliases
|
|
62
|
+
* (typescript/javascript/js → ts). Unknown values pass through unchanged.
|
|
63
|
+
*
|
|
64
|
+
* NOTE: guide flags are intentionally NOT validated against a fixed enum. An
|
|
65
|
+
* unknown/typo'd value simply matches no variant and falls back to the guide's
|
|
66
|
+
* default `file` (the "never fail" behavior from #977). This DIVERGES from
|
|
67
|
+
* `init --platform`, which validates and exits for unsupported values
|
|
68
|
+
* (cli/src/commands/init.ts) — that divergence is deliberate and guide-specific.
|
|
69
|
+
*/
|
|
70
|
+
export function normalizeLanguage(value) {
|
|
71
|
+
if (value === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
const lower = value.trim().toLowerCase();
|
|
74
|
+
if (lower === "")
|
|
75
|
+
return undefined;
|
|
76
|
+
return LANGUAGE_ALIASES[lower] ?? lower;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Normalize a `--platform` flag value: lowercase + trim. No aliases for now.
|
|
80
|
+
* Like `normalizeLanguage`, unknown values pass through and fall back to the
|
|
81
|
+
* default `file` rather than erroring (guide-specific "never fail" behavior).
|
|
82
|
+
*/
|
|
83
|
+
export function normalizePlatform(value) {
|
|
84
|
+
if (value === undefined)
|
|
85
|
+
return undefined;
|
|
86
|
+
const lower = value.trim().toLowerCase();
|
|
87
|
+
if (lower === "")
|
|
88
|
+
return undefined;
|
|
89
|
+
return lower;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Select the guide variant filename for a requested (language, platform).
|
|
93
|
+
*
|
|
94
|
+
* Implemented as ordered `find()` passes — NOT a wildcard scoring system —
|
|
95
|
+
* with the no-flags case special-cased to the default `file`:
|
|
96
|
+
*
|
|
97
|
+
* 0. No flags at all → guide.file (deterministic "default")
|
|
98
|
+
* 1. Exact pair → variant pins BOTH requested dims
|
|
99
|
+
* 2. Language-only → variant pins language; platform agnostic
|
|
100
|
+
* 3. Platform-only → variant pins platform; language agnostic
|
|
101
|
+
* 4. Default → guide.file
|
|
102
|
+
*
|
|
103
|
+
* An omitted REQUEST dimension acts as a wildcard during matching (a
|
|
104
|
+
* `--language swift` request matches a swift variant regardless of platform).
|
|
105
|
+
* This function NEVER throws — an unknown/typo'd/unavailable combination simply
|
|
106
|
+
* falls through to the guide's default `file`.
|
|
107
|
+
*/
|
|
108
|
+
export function selectVariant(guide, request) {
|
|
109
|
+
const reqLang = request.language;
|
|
110
|
+
const reqPlat = request.platform;
|
|
111
|
+
const variants = guide.variants ?? [];
|
|
112
|
+
// 0. No flags supplied at all → deterministically serve the default file.
|
|
113
|
+
if (reqLang === undefined && reqPlat === undefined) {
|
|
114
|
+
return { file: guide.file, matchedOn: "default" };
|
|
115
|
+
}
|
|
116
|
+
// A variant "matches" a dimension if the request omits it (wildcard) OR the
|
|
117
|
+
// variant pins it to exactly the requested value. A variant that omits a
|
|
118
|
+
// dimension is agnostic and matches any requested value for that dimension.
|
|
119
|
+
const langMatches = (v) => reqLang === undefined || v.language === undefined || v.language === reqLang;
|
|
120
|
+
const platMatches = (v) => reqPlat === undefined || v.platform === undefined || v.platform === reqPlat;
|
|
121
|
+
// 1. Exact pair: variant pins both requested dimensions to the request.
|
|
122
|
+
if (reqLang !== undefined && reqPlat !== undefined) {
|
|
123
|
+
const exact = variants.find((v) => v.language === reqLang && v.platform === reqPlat);
|
|
124
|
+
if (exact)
|
|
125
|
+
return { file: exact.file, matchedOn: "exact" };
|
|
126
|
+
}
|
|
127
|
+
// 2. Language requested. Prefer a platform-agnostic variant that pins the
|
|
128
|
+
// language (reported as "language" — pinned one dim, agnostic on the
|
|
129
|
+
// other). If none exists, honor the omitted-platform wildcard and fall
|
|
130
|
+
// back to a pair-specific variant that still satisfies the request
|
|
131
|
+
// (e.g. only `{swift,ios}` published + `--language swift`). That fallback
|
|
132
|
+
// pins BOTH dimensions to satisfy the request, so it's reported as
|
|
133
|
+
// "exact" — the variant is as specific as an exact-pair hit.
|
|
134
|
+
if (reqLang !== undefined) {
|
|
135
|
+
const langAgnostic = variants.find((v) => v.language === reqLang && v.platform === undefined);
|
|
136
|
+
if (langAgnostic)
|
|
137
|
+
return { file: langAgnostic.file, matchedOn: "language" };
|
|
138
|
+
const langWildcard = variants.find((v) => v.language === reqLang && platMatches(v));
|
|
139
|
+
if (langWildcard)
|
|
140
|
+
return { file: langWildcard.file, matchedOn: "exact" };
|
|
141
|
+
}
|
|
142
|
+
// 3. Platform requested (symmetric to pass 2). Prefer a language-agnostic
|
|
143
|
+
// variant that pins the platform ("platform"); otherwise honor the
|
|
144
|
+
// omitted-language wildcard and fall back to a pair-specific variant
|
|
145
|
+
// satisfying the request, reported as "exact".
|
|
146
|
+
if (reqPlat !== undefined) {
|
|
147
|
+
const platAgnostic = variants.find((v) => v.platform === reqPlat && v.language === undefined);
|
|
148
|
+
if (platAgnostic)
|
|
149
|
+
return { file: platAgnostic.file, matchedOn: "platform" };
|
|
150
|
+
const platWildcard = variants.find((v) => v.platform === reqPlat && langMatches(v));
|
|
151
|
+
if (platWildcard)
|
|
152
|
+
return { file: platWildcard.file, matchedOn: "exact" };
|
|
153
|
+
}
|
|
154
|
+
// 4. Nothing matched → the guide's default file (never throws).
|
|
155
|
+
return { file: guide.file, matchedOn: "default" };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The global set of languages a manifest supports. Derived (never hardcoded)
|
|
159
|
+
* from: every guide variant's `language`, the manifest-level `defaults.language`
|
|
160
|
+
* (falling back to `"ts"` when absent), and every `platforms[*].language`. Used
|
|
161
|
+
* to validate `--language` and to render the `guides list` legend / `--json`.
|
|
162
|
+
*
|
|
163
|
+
* Derived per-request from the manifest ACTUALLY loaded (so it stays correct
|
|
164
|
+
* for stale-cache / `--guide-version` fallbacks), and grows automatically as
|
|
165
|
+
* new languages appear in the manifest — no CLI change needed.
|
|
166
|
+
*/
|
|
167
|
+
export function deriveLanguages(manifest) {
|
|
168
|
+
const languages = new Set();
|
|
169
|
+
for (const guide of manifest.guides ?? []) {
|
|
170
|
+
for (const variant of guide.variants ?? []) {
|
|
171
|
+
if (variant.language !== undefined)
|
|
172
|
+
languages.add(variant.language);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
languages.add(manifest.defaults?.language ?? "ts");
|
|
176
|
+
for (const entry of Object.values(manifest.platforms ?? {})) {
|
|
177
|
+
if (entry.language)
|
|
178
|
+
languages.add(entry.language);
|
|
179
|
+
}
|
|
180
|
+
return languages;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Validate the requested `(language, platform)` against the loaded manifest and
|
|
184
|
+
* resolve the effective language (issue #1219). Behavior:
|
|
185
|
+
*
|
|
186
|
+
* - Normalizes both values first (lowercase + trim, aliases, `"" → undefined`)
|
|
187
|
+
* via the existing `normalizeLanguage`/`normalizePlatform` helpers, then
|
|
188
|
+
* validates the NORMALIZED value.
|
|
189
|
+
* - `--language` is validated against `deriveLanguages(manifest)` whether or
|
|
190
|
+
* not a `platforms` block exists (the set is derivable from any manifest).
|
|
191
|
+
* - `--platform` is validated + inferred ONLY when `manifest.platforms` is
|
|
192
|
+
* present. With the block, an unknown platform is a hard error and a known
|
|
193
|
+
* platform infers `language = platforms[p].language` (an explicit
|
|
194
|
+
* `--language` always wins the language dimension). WITHOUT the block, the
|
|
195
|
+
* platform passes through unvalidated and uninferred — today's non-enforcing
|
|
196
|
+
* behavior, so older manifests / stale cache don't start hard-failing.
|
|
197
|
+
* - Unknown values produce a hard-error message mirroring `init --platform`'s
|
|
198
|
+
* style, plus a cross-hint when the bad value names the other dimension
|
|
199
|
+
* (`--platform swift` → "Did you mean --language swift?", and vice versa).
|
|
200
|
+
*/
|
|
201
|
+
export function validateAndResolveRequest(manifest, rawLanguage, rawPlatform) {
|
|
202
|
+
const language = normalizeLanguage(rawLanguage);
|
|
203
|
+
const platform = normalizePlatform(rawPlatform);
|
|
204
|
+
const supportedLanguages = deriveLanguages(manifest);
|
|
205
|
+
const platforms = manifest.platforms; // may be undefined (back-compat)
|
|
206
|
+
// Validate --language (independent of the platforms block).
|
|
207
|
+
if (language !== undefined && !supportedLanguages.has(language)) {
|
|
208
|
+
const supported = [...supportedLanguages].sort().join(", ");
|
|
209
|
+
let message = `Unknown language "${language}". Supported languages: ${supported}`;
|
|
210
|
+
// Cross-hint: the value names a known platform, not a language.
|
|
211
|
+
if (platforms && platforms[language]) {
|
|
212
|
+
message += `. Did you mean --platform ${language}?`;
|
|
213
|
+
}
|
|
214
|
+
return { error: message };
|
|
215
|
+
}
|
|
216
|
+
let effectiveLanguage = language;
|
|
217
|
+
// Validate + infer --platform — active ONLY when the platforms block exists.
|
|
218
|
+
if (platform !== undefined && platforms !== undefined) {
|
|
219
|
+
if (!platforms[platform]) {
|
|
220
|
+
const supported = Object.keys(platforms).sort().join(", ");
|
|
221
|
+
let message = `Unknown platform "${platform}". Supported platforms: ${supported}`;
|
|
222
|
+
// Cross-hint: the value names a known language, not a platform
|
|
223
|
+
// (the reporter's exact `--platform swift` case).
|
|
224
|
+
if (supportedLanguages.has(platform)) {
|
|
225
|
+
message += `. Did you mean --language ${platform}?`;
|
|
226
|
+
}
|
|
227
|
+
return { error: message };
|
|
228
|
+
}
|
|
229
|
+
// Infer the language from the platform; an explicit --language wins.
|
|
230
|
+
effectiveLanguage = language ?? platforms[platform].language;
|
|
231
|
+
}
|
|
232
|
+
// else: no platforms block → back-compat pass-through (no validate, no infer).
|
|
233
|
+
return { language: effectiveLanguage, platform };
|
|
234
|
+
}
|
|
12
235
|
function detectClientVersion() {
|
|
13
236
|
const clientPkgPath = join(process.cwd(), "node_modules", CLIENT_PACKAGE_NAME, "package.json");
|
|
14
237
|
try {
|
|
@@ -56,18 +279,21 @@ function formatClientVersion(info) {
|
|
|
56
279
|
function formatGuidesVersion(info) {
|
|
57
280
|
return info.version;
|
|
58
281
|
}
|
|
59
|
-
|
|
60
|
-
|
|
282
|
+
// Cache paths are segmented by branch (#1463): ~/.primitive/guides/{branch}/
|
|
283
|
+
// {version}/... so a `next` fetch never overwrites the `main` cached copy of
|
|
284
|
+
// the same version+filename (and vice versa).
|
|
285
|
+
function getVersionCacheDir(branch, version) {
|
|
286
|
+
return join(GUIDES_CACHE_DIR, branch, version);
|
|
61
287
|
}
|
|
62
|
-
function getManifestPath(version) {
|
|
63
|
-
return join(getVersionCacheDir(version), "manifest.json");
|
|
288
|
+
function getManifestPath(branch, version) {
|
|
289
|
+
return join(getVersionCacheDir(branch, version), "manifest.json");
|
|
64
290
|
}
|
|
65
|
-
function getGuidesCacheDir(version) {
|
|
66
|
-
return join(getVersionCacheDir(version), "guides");
|
|
291
|
+
function getGuidesCacheDir(branch, version) {
|
|
292
|
+
return join(getVersionCacheDir(branch, version), "guides");
|
|
67
293
|
}
|
|
68
|
-
function ensureCacheDir(version) {
|
|
69
|
-
const versionDir = getVersionCacheDir(version);
|
|
70
|
-
const guidesDir = getGuidesCacheDir(version);
|
|
294
|
+
function ensureCacheDir(branch, version) {
|
|
295
|
+
const versionDir = getVersionCacheDir(branch, version);
|
|
296
|
+
const guidesDir = getGuidesCacheDir(branch, version);
|
|
71
297
|
if (!existsSync(versionDir)) {
|
|
72
298
|
mkdirSync(versionDir, { recursive: true });
|
|
73
299
|
}
|
|
@@ -98,11 +324,15 @@ function isCacheExpired(fetchedAt) {
|
|
|
98
324
|
const fetchedTime = new Date(fetchedAt).getTime();
|
|
99
325
|
return Date.now() - fetchedTime > CACHE_TTL_MS;
|
|
100
326
|
}
|
|
101
|
-
|
|
102
|
-
|
|
327
|
+
/** The raw-GitHub base for a site-repo branch, e.g. `.../primitive-docs-site/main/guides`. */
|
|
328
|
+
function githubRawBase(branch) {
|
|
329
|
+
return `${GITHUB_RAW_ROOT}/${branch}/guides`;
|
|
330
|
+
}
|
|
331
|
+
export function buildManifestUrl(branch, version) {
|
|
332
|
+
return `${githubRawBase(branch)}/${version}/guides.json`;
|
|
103
333
|
}
|
|
104
|
-
function
|
|
105
|
-
return `${
|
|
334
|
+
export function buildGuideUrl(branch, version, fileName) {
|
|
335
|
+
return `${githubRawBase(branch)}/${version}/${fileName}`;
|
|
106
336
|
}
|
|
107
337
|
async function fetchWithTimeout(url, timeoutMs = 10000) {
|
|
108
338
|
const controller = new AbortController();
|
|
@@ -115,11 +345,26 @@ async function fetchWithTimeout(url, timeoutMs = 10000) {
|
|
|
115
345
|
clearTimeout(timeout);
|
|
116
346
|
}
|
|
117
347
|
}
|
|
118
|
-
|
|
119
|
-
|
|
348
|
+
/**
|
|
349
|
+
* Thrown by `fetchManifest` when a branch/version genuinely has no published
|
|
350
|
+
* manifest — every candidate manifest URL returned 404. Distinct from a
|
|
351
|
+
* transport or server error (timeout, DNS, 5xx), which `fetchManifest` lets
|
|
352
|
+
* propagate as a plain `Error`. `resolveManifest` relies on this distinction:
|
|
353
|
+
* only a truly-absent branch may fall back to `main`; a transient failure must
|
|
354
|
+
* surface so an alpha/preview invocation is never silently downgraded to
|
|
355
|
+
* production `main` docs on a flake (#1463).
|
|
356
|
+
*/
|
|
357
|
+
export class ManifestNotFoundError extends Error {
|
|
358
|
+
constructor(message) {
|
|
359
|
+
super(message);
|
|
360
|
+
this.name = "ManifestNotFoundError";
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
async function fetchManifestForVersion(branch, version, forceRefresh = false) {
|
|
364
|
+
ensureCacheDir(branch, version);
|
|
120
365
|
const meta = loadCacheMeta();
|
|
121
|
-
const manifestPath = getManifestPath(version);
|
|
122
|
-
const cacheExpired = isCacheExpired(meta.manifestFetchedAt?.[version]);
|
|
366
|
+
const manifestPath = getManifestPath(branch, version);
|
|
367
|
+
const cacheExpired = isCacheExpired(meta.manifestFetchedAt?.[branch]?.[version]);
|
|
123
368
|
const hasCachedManifest = existsSync(manifestPath);
|
|
124
369
|
// If cache is valid and not forcing refresh, use it
|
|
125
370
|
if (!forceRefresh && !cacheExpired && hasCachedManifest) {
|
|
@@ -132,11 +377,11 @@ async function fetchManifestForVersion(version, forceRefresh = false) {
|
|
|
132
377
|
}
|
|
133
378
|
}
|
|
134
379
|
// Try to fetch from network
|
|
135
|
-
const manifestUrl =
|
|
380
|
+
const manifestUrl = buildManifestUrl(branch, version);
|
|
136
381
|
try {
|
|
137
382
|
const response = await fetchWithTimeout(manifestUrl);
|
|
138
383
|
if (response.status === 404) {
|
|
139
|
-
return null; // Version doesn't exist
|
|
384
|
+
return null; // Version (or branch) doesn't exist
|
|
140
385
|
}
|
|
141
386
|
if (!response.ok) {
|
|
142
387
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
@@ -145,7 +390,8 @@ async function fetchManifestForVersion(version, forceRefresh = false) {
|
|
|
145
390
|
// Save to cache
|
|
146
391
|
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
147
392
|
meta.manifestFetchedAt = meta.manifestFetchedAt || {};
|
|
148
|
-
meta.manifestFetchedAt[
|
|
393
|
+
meta.manifestFetchedAt[branch] = meta.manifestFetchedAt[branch] || {};
|
|
394
|
+
meta.manifestFetchedAt[branch][version] = new Date().toISOString();
|
|
149
395
|
saveCacheMeta(meta);
|
|
150
396
|
return { manifest, fromCache: false, stale: false };
|
|
151
397
|
}
|
|
@@ -163,15 +409,15 @@ async function fetchManifestForVersion(version, forceRefresh = false) {
|
|
|
163
409
|
throw new Error(`Failed to fetch guides manifest: ${err.message}`);
|
|
164
410
|
}
|
|
165
411
|
}
|
|
166
|
-
async function fetchManifest(versionInfo, forceRefresh = false) {
|
|
412
|
+
async function fetchManifest(branch, versionInfo, forceRefresh = false) {
|
|
167
413
|
// Try the requested version first
|
|
168
|
-
const result = await fetchManifestForVersion(versionInfo.version, forceRefresh);
|
|
414
|
+
const result = await fetchManifestForVersion(branch, versionInfo.version, forceRefresh);
|
|
169
415
|
if (result) {
|
|
170
416
|
return { ...result, versionInfo };
|
|
171
417
|
}
|
|
172
418
|
// Version not found, fall back to latest (unless already trying latest)
|
|
173
419
|
if (versionInfo.version !== "latest") {
|
|
174
|
-
const fallbackResult = await fetchManifestForVersion("latest", forceRefresh);
|
|
420
|
+
const fallbackResult = await fetchManifestForVersion(branch, "latest", forceRefresh);
|
|
175
421
|
if (fallbackResult) {
|
|
176
422
|
const fallbackInfo = {
|
|
177
423
|
version: "latest",
|
|
@@ -182,30 +428,133 @@ async function fetchManifest(versionInfo, forceRefresh = false) {
|
|
|
182
428
|
return { ...fallbackResult, versionInfo: fallbackInfo };
|
|
183
429
|
}
|
|
184
430
|
}
|
|
185
|
-
|
|
431
|
+
// Every candidate URL 404'd — the branch/version is genuinely absent. Signal
|
|
432
|
+
// this with a typed error so `resolveManifest` can tell it apart from a
|
|
433
|
+
// transport/server error (which arrives as a plain `Error` from
|
|
434
|
+
// `fetchManifestForVersion`) and only fall back to `main` in the former case.
|
|
435
|
+
throw new ManifestNotFoundError(`Failed to fetch guides manifest for ${versionInfo.version}`);
|
|
186
436
|
}
|
|
187
|
-
|
|
188
|
-
|
|
437
|
+
/**
|
|
438
|
+
* Fetch the manifest for `requestedBranch`, falling back to `main` **only when
|
|
439
|
+
* the non-`main` branch has no published manifest** — i.e. every candidate URL
|
|
440
|
+
* 404s and `fetchManifest` throws a `ManifestNotFoundError`. That fallback
|
|
441
|
+
* covers a `PRIMITIVE_GUIDES_BRANCH` override that names a nonexistent
|
|
442
|
+
* branch.
|
|
443
|
+
*
|
|
444
|
+
* A transport or server error (timeout, DNS, 5xx) on the requested branch is
|
|
445
|
+
* NOT a missing branch — it arrives as a plain `Error` and is re-thrown, never
|
|
446
|
+
* downgraded to `main`. Silently serving production `main` docs for an
|
|
447
|
+
* alpha/preview invocation because of a transient flake would be wrong: a
|
|
448
|
+
* preview run must fail loudly rather than quietly return the wrong branch's
|
|
449
|
+
* docs (#1463).
|
|
450
|
+
*/
|
|
451
|
+
export async function resolveManifest(requestedBranch, versionInfo, forceRefresh = false) {
|
|
452
|
+
try {
|
|
453
|
+
const result = await fetchManifest(requestedBranch, versionInfo, forceRefresh);
|
|
454
|
+
return { ...result, branch: requestedBranch, requestedBranch, branchFellBack: false };
|
|
455
|
+
}
|
|
456
|
+
catch (err) {
|
|
457
|
+
// Only a genuinely-absent branch/version (a 404 on every candidate URL) may
|
|
458
|
+
// fall back to `main`. A transport/server error must surface — see the
|
|
459
|
+
// function doc.
|
|
460
|
+
if (!(err instanceof ManifestNotFoundError)) {
|
|
461
|
+
throw err;
|
|
462
|
+
}
|
|
463
|
+
if (requestedBranch === DEFAULT_DOCS_BRANCH) {
|
|
464
|
+
throw err; // already on main; nothing to fall back to
|
|
465
|
+
}
|
|
466
|
+
const result = await fetchManifest(DEFAULT_DOCS_BRANCH, versionInfo, forceRefresh);
|
|
467
|
+
return {
|
|
468
|
+
...result,
|
|
469
|
+
branch: DEFAULT_DOCS_BRANCH,
|
|
470
|
+
requestedBranch,
|
|
471
|
+
branchFellBack: true,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* One stderr line reporting the served docs branch, only when a non-`main`
|
|
477
|
+
* branch was requested (alpha detection or the override) — a plain `main`
|
|
478
|
+
* target prints nothing so stdout/stderr stay clean for the common case
|
|
479
|
+
* (#1463, behavior 8). Uses `info()` (stderr) so guide content on stdout is
|
|
480
|
+
* never polluted.
|
|
481
|
+
*/
|
|
482
|
+
function reportDocsBranch(resolved) {
|
|
483
|
+
if (resolved.requestedBranch === DEFAULT_DOCS_BRANCH)
|
|
484
|
+
return;
|
|
485
|
+
if (resolved.branchFellBack) {
|
|
486
|
+
info(`Guides branch: ${resolved.requestedBranch} unavailable; using main`);
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
info(`Guides branch: ${resolved.requestedBranch}`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Internal error thrown by `fetchGuide` on a non-ok guide-file response. NOT
|
|
494
|
+
* exported. `status` lets the `get` action distinguish a 404 (the cached
|
|
495
|
+
* manifest may name a file that was renamed/removed upstream — heal by
|
|
496
|
+
* refetching the manifest) from a transport error (keep the stale-cache
|
|
497
|
+
* fallback). `staleContent` carries the on-disk copy for the failed filename,
|
|
498
|
+
* if any, so the action can serve it as the option-(b) last resort (#1034).
|
|
499
|
+
*/
|
|
500
|
+
class GuideFetchError extends Error {
|
|
501
|
+
status;
|
|
502
|
+
selectedFile;
|
|
503
|
+
staleContent;
|
|
504
|
+
constructor(status, message, selectedFile, staleContent) {
|
|
505
|
+
super(message);
|
|
506
|
+
this.status = status;
|
|
507
|
+
this.selectedFile = selectedFile;
|
|
508
|
+
this.staleContent = staleContent;
|
|
509
|
+
this.name = "GuideFetchError";
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/** Read an on-disk cached guide file, or undefined if absent/corrupted. */
|
|
513
|
+
function readCachedGuide(cachedPath) {
|
|
514
|
+
if (!existsSync(cachedPath))
|
|
515
|
+
return undefined;
|
|
516
|
+
try {
|
|
517
|
+
return readFileSync(cachedPath, "utf-8");
|
|
518
|
+
}
|
|
519
|
+
catch {
|
|
520
|
+
return undefined; // corrupted cache
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
async function fetchGuide(guide, branch, version, request = {}, forceRefresh = false) {
|
|
524
|
+
ensureCacheDir(branch, version);
|
|
189
525
|
const meta = loadCacheMeta();
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
const
|
|
526
|
+
// Resolve the variant filename for the requested (language, platform).
|
|
527
|
+
const { file: selectedFile } = selectVariant(guide, request);
|
|
528
|
+
const guideFileName = basename(selectedFile);
|
|
529
|
+
const cachedPath = join(getGuidesCacheDir(branch, version), guideFileName);
|
|
530
|
+
// Freshness is keyed by branch + the RESOLVED filename (not topic): a `ts`
|
|
531
|
+
// fetch cannot mark the `swift` variant fresh (#977), and a `next` fetch
|
|
532
|
+
// cannot mark the `main` copy of the same file fresh (#1463, behavior 7).
|
|
533
|
+
const fetchedAt = meta.guidesFetchedAt?.[branch]?.[version]?.[guideFileName];
|
|
193
534
|
const cacheExpired = isCacheExpired(fetchedAt);
|
|
194
535
|
const hasCachedGuide = existsSync(cachedPath);
|
|
195
536
|
// If cache is valid and not forcing refresh, use it
|
|
196
537
|
if (!forceRefresh && !cacheExpired && hasCachedGuide) {
|
|
197
538
|
try {
|
|
198
539
|
const content = readFileSync(cachedPath, "utf-8");
|
|
199
|
-
return { content, fromCache: true, stale: false };
|
|
540
|
+
return { content, fromCache: true, stale: false, selectedFile };
|
|
200
541
|
}
|
|
201
542
|
catch {
|
|
202
543
|
// Cache corrupted, will fetch fresh
|
|
203
544
|
}
|
|
204
545
|
}
|
|
205
546
|
// Try to fetch from network
|
|
206
|
-
const guideUrl =
|
|
547
|
+
const guideUrl = buildGuideUrl(branch, version, selectedFile);
|
|
207
548
|
try {
|
|
208
549
|
const response = await fetchWithTimeout(guideUrl);
|
|
550
|
+
if (response.status === 404) {
|
|
551
|
+
// The cache-derived filename no longer exists upstream (a docs rename /
|
|
552
|
+
// removal). Let the 404 escape to the `get` action so it can refetch the
|
|
553
|
+
// manifest and re-resolve — heal must take priority over serving stale.
|
|
554
|
+
// Attach the on-disk copy (if any) so the action can serve it as the
|
|
555
|
+
// option-(b) last resort when the heal can't help (#1034).
|
|
556
|
+
throw new GuideFetchError(404, `Failed to fetch guide "${guide.topic}": HTTP 404: ${response.statusText}`, selectedFile, readCachedGuide(cachedPath));
|
|
557
|
+
}
|
|
209
558
|
if (!response.ok) {
|
|
210
559
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
211
560
|
}
|
|
@@ -213,40 +562,101 @@ async function fetchGuide(guide, version, forceRefresh = false) {
|
|
|
213
562
|
// Save to cache
|
|
214
563
|
writeFileSync(cachedPath, content);
|
|
215
564
|
meta.guidesFetchedAt = meta.guidesFetchedAt || {};
|
|
216
|
-
meta.guidesFetchedAt[
|
|
217
|
-
meta.guidesFetchedAt[
|
|
565
|
+
meta.guidesFetchedAt[branch] = meta.guidesFetchedAt[branch] || {};
|
|
566
|
+
meta.guidesFetchedAt[branch][version] = meta.guidesFetchedAt[branch][version] || {};
|
|
567
|
+
meta.guidesFetchedAt[branch][version][guideFileName] = new Date().toISOString();
|
|
218
568
|
saveCacheMeta(meta);
|
|
219
|
-
return { content, fromCache: false, stale: false };
|
|
569
|
+
return { content, fromCache: false, stale: false, selectedFile };
|
|
220
570
|
}
|
|
221
571
|
catch (err) {
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
572
|
+
// A 404 must reach the action (heal takes priority over stale) — re-throw it
|
|
573
|
+
// unchanged rather than serving the stale copy here.
|
|
574
|
+
if (err instanceof GuideFetchError && err.status === 404) {
|
|
575
|
+
throw err;
|
|
576
|
+
}
|
|
577
|
+
// Non-404 (transport error / 5xx): serve the stale on-disk copy if present
|
|
578
|
+
// (Fork F — transport problems are not manifest skew), else throw.
|
|
579
|
+
const stale = readCachedGuide(cachedPath);
|
|
580
|
+
if (stale !== undefined) {
|
|
581
|
+
return { content: stale, fromCache: true, stale: true, selectedFile };
|
|
231
582
|
}
|
|
232
583
|
throw new Error(`Failed to fetch guide "${guide.topic}": ${err.message}`);
|
|
233
584
|
}
|
|
234
585
|
}
|
|
586
|
+
/** Normalize a guide's variants for display / --json output. */
|
|
587
|
+
function normalizeVariants(guide) {
|
|
588
|
+
return (guide.variants ?? []).map((v) => ({
|
|
589
|
+
...(v.language !== undefined ? { language: v.language } : {}),
|
|
590
|
+
...(v.platform !== undefined ? { platform: v.platform } : {}),
|
|
591
|
+
file: v.file,
|
|
592
|
+
}));
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Compact rendering of the LANGUAGES a guide offers, for the `guides list`
|
|
596
|
+
* table (issue #1219 renamed the ambiguous `COMBINATIONS` column to
|
|
597
|
+
* `LANGUAGES`). Lists each variant's `language`, de-duplicated and order-
|
|
598
|
+
* preserving; a guide with no language-pinned variants renders `default`.
|
|
599
|
+
* Platforms are surfaced separately as a manifest-level legend, since they are
|
|
600
|
+
* a manifest-wide map rather than a per-guide attribute.
|
|
601
|
+
*/
|
|
602
|
+
export function formatLanguages(guide) {
|
|
603
|
+
const variants = guide.variants ?? [];
|
|
604
|
+
if (variants.length === 0)
|
|
605
|
+
return "default";
|
|
606
|
+
const languages = variants
|
|
607
|
+
.map((v) => v.language)
|
|
608
|
+
.filter((l) => l !== undefined);
|
|
609
|
+
if (languages.length === 0)
|
|
610
|
+
return "default";
|
|
611
|
+
return [...new Set(languages)].join("; ");
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Render the manifest-level platform→language legend for `guides list`, e.g.
|
|
615
|
+
* `web -> ts, ios -> swift, macos -> swift`. When the manifest has no
|
|
616
|
+
* `platforms` block (older manifests / stale cache, before
|
|
617
|
+
* Primitive-Labs/primitive-docs#194), platforms aren't modeled yet, so this
|
|
618
|
+
* reports `none defined yet` rather than implying a map that doesn't exist.
|
|
619
|
+
*/
|
|
620
|
+
function formatPlatformsLegend(manifest) {
|
|
621
|
+
const platforms = manifest.platforms;
|
|
622
|
+
if (!platforms || Object.keys(platforms).length === 0) {
|
|
623
|
+
return "none defined yet";
|
|
624
|
+
}
|
|
625
|
+
return Object.entries(platforms)
|
|
626
|
+
.map(([platform, entry]) => `${platform} -> ${entry.language}`)
|
|
627
|
+
.join(", ");
|
|
628
|
+
}
|
|
235
629
|
export function registerGuidesCommands(program) {
|
|
236
630
|
const guides = program
|
|
237
631
|
.command("guides")
|
|
238
632
|
.description("Access Primitive how-to guides for building apps")
|
|
239
633
|
.addHelpText("after", `
|
|
240
634
|
Examples:
|
|
241
|
-
$ primitive guides list
|
|
242
|
-
$ primitive guides list --json
|
|
243
|
-
$ primitive guides get documents
|
|
244
|
-
$ primitive guides get
|
|
245
|
-
$ primitive guides
|
|
635
|
+
$ primitive guides list # List available guides + languages
|
|
636
|
+
$ primitive guides list --json # List as JSON for programmatic use
|
|
637
|
+
$ primitive guides get documents # Fetch the default (TS) documents guide
|
|
638
|
+
$ primitive guides get documents --language swift # Fetch the Swift variant
|
|
639
|
+
$ primitive guides get documents --platform ios # Fetch the platform's language (e.g. Swift)
|
|
640
|
+
$ primitive guides list --guide-version 1 # List guides for client v1
|
|
641
|
+
$ primitive guides list --guide-version latest # List guides for the latest line
|
|
642
|
+
|
|
643
|
+
Language & platform:
|
|
644
|
+
--language <ts|swift|...> fetches a specific language variant of a guide.
|
|
645
|
+
Aliases: typescript/javascript/js -> ts.
|
|
646
|
+
--platform <web|ios|macos|...> selects the language a platform targets: the
|
|
647
|
+
guides manifest maps each platform to its language (e.g. ios -> swift), so
|
|
648
|
+
'guides get documents --platform ios' returns the Swift guide. An explicit
|
|
649
|
+
--language always wins the language dimension.
|
|
650
|
+
Unknown --language values (always), and unknown --platform values (once the
|
|
651
|
+
manifest publishes its platform map), are rejected with a clear error listing
|
|
652
|
+
the supported values — mirroring 'init --platform'. Run 'guides list' to see
|
|
653
|
+
the supported languages and platforms.
|
|
246
654
|
|
|
247
655
|
Versioning:
|
|
248
656
|
By default, guides are fetched for the detected ${CLIENT_PACKAGE_NAME} version.
|
|
249
|
-
Use --version to explicitly request a specific major version.
|
|
657
|
+
Use --guide-version to explicitly request a specific major version (e.g. 1, 2,
|
|
658
|
+
or 'latest'). (The flag is --guide-version, not --version, because the root
|
|
659
|
+
'primitive --version' prints the CLI version and would otherwise swallow it.)
|
|
250
660
|
Falls back to 'latest' if the version is not found.
|
|
251
661
|
|
|
252
662
|
Cache:
|
|
@@ -259,11 +669,28 @@ Cache:
|
|
|
259
669
|
.description("List available guides")
|
|
260
670
|
.option("--json", "Output as JSON")
|
|
261
671
|
.option("--refresh", "Force refresh from network")
|
|
262
|
-
.option("--version <version>", "Fetch guides for a specific major version (e.g., 1, 2, or 'latest')")
|
|
672
|
+
.option("--guide-version <version>", "Fetch guides for a specific major version (e.g., 1, 2, or 'latest')")
|
|
673
|
+
.option("--language <lang>", "Language to validate (ts, swift, ...; informational on list)")
|
|
674
|
+
.option("--platform <platform>", "Platform to validate (web, ios, macos, ...; informational on list)")
|
|
263
675
|
.action(async (options) => {
|
|
264
676
|
try {
|
|
265
|
-
const requestedVersion = resolveVersion(options.
|
|
266
|
-
const
|
|
677
|
+
const requestedVersion = resolveVersion(options.guideVersion);
|
|
678
|
+
const requestedBranch = resolveDocsBranchForInvocation();
|
|
679
|
+
const resolved = await resolveManifest(requestedBranch, requestedVersion, options.refresh);
|
|
680
|
+
const { manifest, stale, versionInfo, branch } = resolved;
|
|
681
|
+
// Validate the requested dimensions against the loaded manifest (same
|
|
682
|
+
// rules as `get`, for one mental model — Q3). They have no per-row
|
|
683
|
+
// effect on `list`, but an unknown value should fail loudly here too.
|
|
684
|
+
const resolution = validateAndResolveRequest(manifest, options.language, options.platform);
|
|
685
|
+
if (resolution.error) {
|
|
686
|
+
error(resolution.error);
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
const reqLanguage = normalizeLanguage(options.language);
|
|
690
|
+
const reqPlatform = normalizePlatform(options.platform);
|
|
691
|
+
const supportedLanguages = [...deriveLanguages(manifest)].sort();
|
|
692
|
+
// Report the served docs branch (stderr) when alpha/override is involved.
|
|
693
|
+
reportDocsBranch(resolved);
|
|
267
694
|
if (stale) {
|
|
268
695
|
warn("Using stale cache (network unavailable)");
|
|
269
696
|
}
|
|
@@ -271,6 +698,12 @@ Cache:
|
|
|
271
698
|
json({
|
|
272
699
|
version: versionInfo.version,
|
|
273
700
|
versionSource: versionInfo.source,
|
|
701
|
+
guidesBranch: branch,
|
|
702
|
+
defaults: manifest.defaults ?? null,
|
|
703
|
+
platforms: manifest.platforms ?? null,
|
|
704
|
+
supportedLanguages,
|
|
705
|
+
language: reqLanguage ?? null,
|
|
706
|
+
platform: reqPlatform ?? null,
|
|
274
707
|
guides: manifest.guides.map((g) => ({
|
|
275
708
|
topic: g.topic,
|
|
276
709
|
description: g.description,
|
|
@@ -279,6 +712,7 @@ Cache:
|
|
|
279
712
|
concepts: g.concepts,
|
|
280
713
|
prerequisites: g.prerequisites,
|
|
281
714
|
relatedGuides: g.relatedGuides,
|
|
715
|
+
availableVariants: normalizeVariants(g),
|
|
282
716
|
})),
|
|
283
717
|
});
|
|
284
718
|
return;
|
|
@@ -289,14 +723,41 @@ Cache:
|
|
|
289
723
|
}
|
|
290
724
|
keyValue("Client version", formatClientVersion(versionInfo));
|
|
291
725
|
keyValue("Guides version", formatGuidesVersion(versionInfo));
|
|
726
|
+
// Manifest-level legend: spell out languages and the platform→language
|
|
727
|
+
// map explicitly, so `--language`/`--platform` are no longer ambiguous.
|
|
728
|
+
keyValue("Languages", supportedLanguages.join(", "));
|
|
729
|
+
keyValue("Platforms", formatPlatformsLegend(manifest));
|
|
292
730
|
console.log("");
|
|
293
|
-
|
|
731
|
+
const rows = manifest.guides.map((g) => ({
|
|
732
|
+
topic: g.topic,
|
|
733
|
+
description: g.description,
|
|
734
|
+
languages: formatLanguages(g),
|
|
735
|
+
}));
|
|
736
|
+
// Size DESCRIPTION from the live terminal width so wide terminals show
|
|
737
|
+
// the full description (the guides.json contract budgets descriptions
|
|
738
|
+
// at <=100 chars; DESCRIPTION_MAX mirrors that cap — see
|
|
739
|
+
// primitive-docs `scripts/sync-guides-json.mjs`) while the table never
|
|
740
|
+
// wraps. Floor 48 keeps today's readable width on an 80-col terminal;
|
|
741
|
+
// on a non-TTY (piped) the column is sized to the cap so full
|
|
742
|
+
// descriptions reach `grep`/agents. `truncate` defensively clips a
|
|
743
|
+
// description that violates the upstream contract.
|
|
744
|
+
const DESCRIPTION_MAX = 100;
|
|
745
|
+
console.log(formatTable(rows, [
|
|
294
746
|
{ header: "TOPIC", key: "topic" },
|
|
295
|
-
{
|
|
747
|
+
{
|
|
748
|
+
header: "DESCRIPTION",
|
|
749
|
+
key: "description",
|
|
750
|
+
flex: true,
|
|
751
|
+
truncate: true,
|
|
752
|
+
flexFloor: 48,
|
|
753
|
+
flexMax: DESCRIPTION_MAX,
|
|
754
|
+
},
|
|
755
|
+
{ header: "LANGUAGES", key: "languages" },
|
|
296
756
|
]));
|
|
297
757
|
console.log("");
|
|
298
|
-
keyValue("Cache location", getVersionCacheDir(versionInfo.version));
|
|
299
|
-
info("Use 'primitive guides get <topic>' to fetch a guide.");
|
|
758
|
+
keyValue("Cache location", getVersionCacheDir(branch, versionInfo.version));
|
|
759
|
+
info("Use 'primitive guides get <topic> --language <ts|swift>' to fetch a guide.");
|
|
760
|
+
info("Example: primitive guides get documents --language swift");
|
|
300
761
|
}
|
|
301
762
|
catch (err) {
|
|
302
763
|
error(err.message);
|
|
@@ -310,22 +771,50 @@ Cache:
|
|
|
310
771
|
.argument("<topic>", "Guide topic (e.g., documents, workflows, prompts)")
|
|
311
772
|
.option("--json", "Output metadata as JSON instead of content")
|
|
312
773
|
.option("--refresh", "Force refresh from network")
|
|
313
|
-
.option("--version <version>", "Fetch guides for a specific major version (e.g., 1, 2, or 'latest')")
|
|
774
|
+
.option("--guide-version <version>", "Fetch guides for a specific major version (e.g., 1, 2, or 'latest')")
|
|
775
|
+
.option("--language <lang>", "Language variant to fetch (ts, swift, ...; aliases typescript/javascript/js -> ts)")
|
|
776
|
+
.option("--platform <platform>", "Platform whose language to fetch (web, ios, macos, ...; inferred from the manifest)")
|
|
314
777
|
.action(async (topic, options) => {
|
|
315
778
|
try {
|
|
316
|
-
const requestedVersion = resolveVersion(options.
|
|
317
|
-
const
|
|
779
|
+
const requestedVersion = resolveVersion(options.guideVersion);
|
|
780
|
+
const requestedBranch = resolveDocsBranchForInvocation();
|
|
781
|
+
const resolved = await resolveManifest(requestedBranch, requestedVersion, options.refresh);
|
|
782
|
+
const { manifest, fromCache, stale: manifestStale, versionInfo, branch } = resolved;
|
|
318
783
|
const guide = manifest.guides.find((g) => g.topic.toLowerCase() === topic.toLowerCase());
|
|
319
784
|
if (!guide) {
|
|
320
785
|
const availableTopics = manifest.guides.map((g) => g.topic).join(", ");
|
|
321
786
|
error(`Guide "${topic}" not found. Available topics: ${availableTopics}`);
|
|
322
787
|
process.exit(1);
|
|
323
788
|
}
|
|
789
|
+
// Validate both flags against the loaded manifest and resolve the
|
|
790
|
+
// effective language (issue #1219). An unknown value is a hard error;
|
|
791
|
+
// `--platform ios` infers its language (e.g. swift) when the manifest
|
|
792
|
+
// publishes a `platforms` block, while an explicit `--language` wins.
|
|
793
|
+
const resolution = validateAndResolveRequest(manifest, options.language, options.platform);
|
|
794
|
+
if (resolution.error) {
|
|
795
|
+
error(resolution.error);
|
|
796
|
+
process.exit(1);
|
|
797
|
+
}
|
|
798
|
+
// `reqLanguage` is the EFFECTIVE language after platform inference; pass
|
|
799
|
+
// it to both the JSON selection and the content fetch so they can't
|
|
800
|
+
// desynchronize.
|
|
801
|
+
const reqLanguage = resolution.language;
|
|
802
|
+
const reqPlatform = resolution.platform;
|
|
803
|
+
const requestedAnyDimension = normalizeLanguage(options.language) !== undefined ||
|
|
804
|
+
normalizePlatform(options.platform) !== undefined;
|
|
805
|
+
const selection = selectVariant(guide, {
|
|
806
|
+
language: reqLanguage,
|
|
807
|
+
platform: reqPlatform,
|
|
808
|
+
});
|
|
809
|
+
// Report the served docs branch (stderr) when alpha/override is involved.
|
|
810
|
+
reportDocsBranch(resolved);
|
|
324
811
|
if (options.json) {
|
|
325
|
-
// Output just metadata
|
|
812
|
+
// Output just metadata — does NOT fetch content. `selectedFile` is the
|
|
813
|
+
// file that WOULD be served for this request (see selection above).
|
|
326
814
|
json({
|
|
327
815
|
version: versionInfo.version,
|
|
328
816
|
versionSource: versionInfo.source,
|
|
817
|
+
guidesBranch: branch,
|
|
329
818
|
topic: guide.topic,
|
|
330
819
|
description: guide.description,
|
|
331
820
|
keywords: guide.keywords,
|
|
@@ -333,16 +822,79 @@ Cache:
|
|
|
333
822
|
concepts: guide.concepts,
|
|
334
823
|
prerequisites: guide.prerequisites,
|
|
335
824
|
relatedGuides: guide.relatedGuides,
|
|
336
|
-
|
|
825
|
+
language: reqLanguage ?? null,
|
|
826
|
+
platform: reqPlatform ?? null,
|
|
827
|
+
defaultPlatform: manifest.defaults?.platform ?? null,
|
|
828
|
+
selectedFile: selection.file,
|
|
829
|
+
matchedOn: selection.matchedOn,
|
|
830
|
+
availableVariants: normalizeVariants(guide),
|
|
831
|
+
cacheLocation: join(getGuidesCacheDir(branch, versionInfo.version), basename(selection.file)),
|
|
337
832
|
});
|
|
338
833
|
return;
|
|
339
834
|
}
|
|
340
|
-
const {
|
|
341
|
-
|
|
835
|
+
const request = { language: reqLanguage, platform: reqPlatform };
|
|
836
|
+
let result;
|
|
837
|
+
try {
|
|
838
|
+
result = await fetchGuide(guide, branch, versionInfo.version, request, options.refresh);
|
|
839
|
+
}
|
|
840
|
+
catch (err) {
|
|
841
|
+
// Only a 404 is healable here. Transport errors already served stale
|
|
842
|
+
// (or threw) inside fetchGuide; rethrow anything that isn't a 404.
|
|
843
|
+
if (!(err instanceof GuideFetchError && err.status === 404)) {
|
|
844
|
+
throw err;
|
|
845
|
+
}
|
|
846
|
+
// Heal only when the manifest came from cache — then the named file
|
|
847
|
+
// may have been renamed/removed upstream while our manifest is stale.
|
|
848
|
+
// Force a manifest refetch, re-find the topic, and retry the file
|
|
849
|
+
// ONCE. A fresh-from-network manifest (incl. --refresh) makes the 404
|
|
850
|
+
// authoritative, so we skip straight to the last resort below (#1034).
|
|
851
|
+
if (fromCache) {
|
|
852
|
+
try {
|
|
853
|
+
// Heal on the SAME branch the manifest resolved on, so the
|
|
854
|
+
// manifest and guide never disagree (#1463, behavior 4).
|
|
855
|
+
const refreshed = await fetchManifest(branch, requestedVersion, /* forceRefresh */ true);
|
|
856
|
+
const freshGuide = refreshed.manifest.guides.find((g) => g.topic.toLowerCase() === topic.toLowerCase());
|
|
857
|
+
if (freshGuide) {
|
|
858
|
+
// Re-calling fetchGuide recomputes selectVariant + the cache path
|
|
859
|
+
// for the (possibly new) filename — no extra resolve site. If the
|
|
860
|
+
// filename is unchanged it just 404s again and falls through.
|
|
861
|
+
const retried = await fetchGuide(freshGuide, branch, refreshed.versionInfo.version, request, options.refresh);
|
|
862
|
+
if (basename(retried.selectedFile) !== basename(err.selectedFile)) {
|
|
863
|
+
warn(`Guide "${topic}" file moved upstream; refreshed manifest and retried.`);
|
|
864
|
+
}
|
|
865
|
+
result = retried;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
catch {
|
|
869
|
+
// Heal failed (manifest refetch failed / retry 404'd / topic gone)
|
|
870
|
+
// → fall through to the option-(b) last resort on the ORIGINAL error.
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
// Option (b) last resort — applies to ALL 404s, not just cached-manifest
|
|
874
|
+
// ones, so `--refresh`-on-404 still serves an old on-disk copy as it
|
|
875
|
+
// does today. Serve the old copy if we have one; otherwise surface the
|
|
876
|
+
// original terminal 404.
|
|
877
|
+
if (!result) {
|
|
878
|
+
if (err.staleContent !== undefined) {
|
|
879
|
+
result = { content: err.staleContent, stale: true, selectedFile: err.selectedFile };
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
882
|
+
throw err;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (manifestStale || result.stale) {
|
|
342
887
|
warn("Using stale cache (network unavailable)");
|
|
343
888
|
}
|
|
889
|
+
// Transparency note (issue #1219): a language/platform was requested but
|
|
890
|
+
// this topic has no variant pinning it, so the default file was served.
|
|
891
|
+
// This closes the residual silent-fallback footgun beyond `--platform`.
|
|
892
|
+
// The note goes to STDERR (via `info`) so stdout stays clean for piping.
|
|
893
|
+
if (requestedAnyDimension && selection.matchedOn === "default") {
|
|
894
|
+
info(`No ${reqLanguage ?? reqPlatform} variant for "${guide.topic}"; served the default guide.`);
|
|
895
|
+
}
|
|
344
896
|
// Print the guide content directly to stdout
|
|
345
|
-
console.log(content);
|
|
897
|
+
console.log(result.content);
|
|
346
898
|
}
|
|
347
899
|
catch (err) {
|
|
348
900
|
error(err.message);
|