0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-16 21:46:22 -05:00

refactor: rework supportedAstroFeatures (#11806)

* refactor: rework supportAstroFeatures

* fix: build

* fix: tests

* chore: changeset
This commit is contained in:
Erika 2024-09-13 13:58:57 +02:00 committed by GitHub
parent ee38b3a946
commit f7f2338c2b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 131 additions and 168 deletions

View file

@ -0,0 +1,7 @@
---
'astro': major
---
Removes the `assets` property on `supportedAstroFeatures` for adapters, as it did not reflect reality properly in many cases.
Now, relating to assets, only a single `sharpImageService` property is available, determining if the adapter is compatible with the built-in sharp image service.

View file

@ -0,0 +1,7 @@
---
'astro': minor
---
The value of the different properties on `supportedAstroFeatures` for adapters can now be objects, with a `support` and `message` properties. The content of the `message` property will be shown in the Astro CLI when the adapter is not compatible with the feature, allowing one to give a better informational message to the user.
This is notably useful with the new `limited` value, to explain to the user why support is limited.

View file

@ -0,0 +1,5 @@
---
'astro': minor
---
Adds a new `limited` value for the different properties of `supportedAstroFeatures` for adapters, which indicates that the adapter is compatible with the feature, but with some limitations. This is useful for adapters that support a feature, but not in all cases or with all options.

View file

@ -5,6 +5,7 @@ import type { Plugin as VitePlugin } from 'vite';
import { getAssetsPrefix } from '../../../assets/utils/getAssetsPrefix.js';
import { normalizeTheLocale } from '../../../i18n/index.js';
import { toFallbackType, toRoutingStrategy } from '../../../i18n/utils.js';
import { unwrapSupportKind } from '../../../integrations/features-validation.js';
import { runHookBuildSsr } from '../../../integrations/hooks.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
import type {
@ -273,6 +274,7 @@ function buildManifest(
serverIslandNameMap: Array.from(settings.serverIslandNameMap),
key: encodedKey,
envGetSecretEnabled:
(settings.adapter?.supportedAstroFeatures.envGetSecret ?? 'unsupported') !== 'unsupported',
(unwrapSupportKind(settings.adapter?.supportedAstroFeatures.envGetSecret) ??
'unsupported') !== 'unsupported',
};
}

View file

@ -33,6 +33,7 @@ export type LoggerLabel =
| 'assets'
| 'env'
| 'update'
| 'adapter'
// SKIP_FORMAT: A special label that tells the logger not to apply any formatting.
// Useful for messages that are already formatted, like the server start message.
| 'SKIP_FORMAT';

View file

@ -1,22 +1,18 @@
import type { Logger } from '../core/logger/core.js';
import type { AstroSettings } from '../types/astro.js';
import type { AstroConfig } from '../types/public/config.js';
import type {
AdapterSupport,
AdapterSupportsKind,
AstroAdapterFeatureMap,
AstroAdapterFeatures,
AstroAssetsFeature,
} from '../types/public/integrations.js';
const STABLE = 'stable';
const DEPRECATED = 'deprecated';
const UNSUPPORTED = 'unsupported';
const EXPERIMENTAL = 'experimental';
const UNSUPPORTED_ASSETS_FEATURE: AstroAssetsFeature = {
supportKind: UNSUPPORTED,
isSharpCompatible: false,
};
export const AdapterFeatureStability = {
STABLE: 'stable',
DEPRECATED: 'deprecated',
UNSUPPORTED: 'unsupported',
EXPERIMENTAL: 'experimental',
LIMITED: 'limited',
} as const;
type ValidationResult = {
[Property in keyof AstroAdapterFeatureMap]: boolean;
@ -33,16 +29,15 @@ export function validateSupportedFeatures(
adapterName: string,
featureMap: AstroAdapterFeatureMap,
settings: AstroSettings,
adapterFeatures: AstroAdapterFeatures | undefined,
logger: Logger,
): ValidationResult {
const {
assets = UNSUPPORTED_ASSETS_FEATURE,
serverOutput = UNSUPPORTED,
staticOutput = UNSUPPORTED,
hybridOutput = UNSUPPORTED,
i18nDomains = UNSUPPORTED,
envGetSecret = UNSUPPORTED,
serverOutput = AdapterFeatureStability.UNSUPPORTED,
staticOutput = AdapterFeatureStability.UNSUPPORTED,
hybridOutput = AdapterFeatureStability.UNSUPPORTED,
i18nDomains = AdapterFeatureStability.UNSUPPORTED,
envGetSecret = AdapterFeatureStability.UNSUPPORTED,
sharpImageService = AdapterFeatureStability.UNSUPPORTED,
} = featureMap;
const validationResult: ValidationResult = {};
@ -67,9 +62,8 @@ export function validateSupportedFeatures(
adapterName,
logger,
'serverOutput',
() => settings.config?.output === 'server',
() => settings.config?.output === 'server' || settings.buildOutput === 'server',
);
validationResult.assets = validateAssetsFeature(assets, adapterName, settings.config, logger);
if (settings.config.i18n?.domains) {
validationResult.i18nDomains = validateSupportKind(
@ -91,71 +85,93 @@ export function validateSupportedFeatures(
() => Object.keys(settings.config?.env?.schema ?? {}).length !== 0,
);
validationResult.sharpImageService = validateSupportKind(
sharpImageService,
adapterName,
logger,
'sharp',
() => settings.config?.image?.service?.entrypoint === 'astro/assets/services/sharp',
);
return validationResult;
}
export function unwrapSupportKind(supportKind?: AdapterSupport): AdapterSupportsKind | undefined {
if (!supportKind) {
return undefined;
}
return typeof supportKind === 'object' ? supportKind.support : supportKind;
}
export function getSupportMessage(supportKind: AdapterSupport): string | undefined {
return typeof supportKind === 'object' ? supportKind.message : undefined;
}
function validateSupportKind(
supportKind: AdapterSupportsKind,
supportKind: AdapterSupport,
adapterName: string,
logger: Logger,
featureName: string,
hasCorrectConfig: () => boolean,
): boolean {
if (supportKind === STABLE) {
return true;
} else if (supportKind === DEPRECATED) {
featureIsDeprecated(adapterName, logger, featureName);
} else if (supportKind === EXPERIMENTAL) {
featureIsExperimental(adapterName, logger, featureName);
}
const supportValue = unwrapSupportKind(supportKind);
const message = getSupportMessage(supportKind);
if (hasCorrectConfig() && supportKind === UNSUPPORTED) {
featureIsUnsupported(adapterName, logger, featureName);
if (!supportValue) {
return false;
} else {
return true;
}
if (supportValue === AdapterFeatureStability.STABLE) {
return true;
} else if (hasCorrectConfig()) {
// If the user has the relevant configuration, but the adapter doesn't support it, warn the user
logFeatureSupport(adapterName, logger, featureName, supportValue, message);
}
return false;
}
function featureIsUnsupported(adapterName: string, logger: Logger, featureName: string) {
logger.error(
'config',
`The adapter ${adapterName} doesn't currently support the feature "${featureName}".`,
);
}
function featureIsExperimental(adapterName: string, logger: Logger, featureName: string) {
logger.warn(
'config',
`The adapter ${adapterName} provides experimental support for "${featureName}". You may experience issues or breaking changes until this feature is fully supported by the adapter.`,
);
}
function featureIsDeprecated(adapterName: string, logger: Logger, featureName: string) {
logger.warn(
'config',
`The adapter ${adapterName} has deprecated its support for "${featureName}", and future compatibility is not guaranteed. The adapter may completely remove support for this feature without warning.`,
);
}
const SHARP_SERVICE = 'astro/assets/services/sharp';
function validateAssetsFeature(
assets: AstroAssetsFeature,
function logFeatureSupport(
adapterName: string,
config: AstroConfig,
logger: Logger,
): boolean {
const { supportKind = UNSUPPORTED, isSharpCompatible = false } = assets;
if (config?.image?.service?.entrypoint === SHARP_SERVICE && !isSharpCompatible) {
logger.warn(
null,
`The currently selected adapter \`${adapterName}\` is not compatible with the image service "Sharp".`,
);
return false;
featureName: string,
supportKind: AdapterSupport,
adapterMessage?: string,
) {
switch (supportKind) {
case AdapterFeatureStability.STABLE:
break;
case AdapterFeatureStability.DEPRECATED:
logger.warn(
'config',
`The adapter ${adapterName} has deprecated its support for "${featureName}", and future compatibility is not guaranteed. The adapter may completely remove support for this feature without warning.`,
);
break;
case AdapterFeatureStability.EXPERIMENTAL:
logger.warn(
'config',
`The adapter ${adapterName} provides experimental support for "${featureName}". You may experience issues or breaking changes until this feature is fully supported by the adapter.`,
);
break;
case AdapterFeatureStability.LIMITED:
logger.warn(
'config',
`The adapter ${adapterName} has limited support for "${featureName}". Certain features may not work as expected.`,
);
break;
case AdapterFeatureStability.UNSUPPORTED:
logger.error(
'config',
`The adapter ${adapterName} does not currently support the feature "${featureName}". Your project may not build correctly.`,
);
break;
}
return validateSupportKind(supportKind, adapterName, logger, 'assets', () => true);
// If the adapter specified a custom message, log it after the default message
if (adapterMessage) {
logger.warn('adapter', adapterMessage);
}
}
export function getAdapterStaticRecommendation(adapterName: string): string | undefined {

View file

@ -323,26 +323,12 @@ export async function runHookConfigDone({
`The adapter ${adapter.name} doesn't provide a feature map. It is required in Astro 4.0.`,
);
} else {
const validationResult = validateSupportedFeatures(
validateSupportedFeatures(
adapter.name,
adapter.supportedAstroFeatures,
settings,
// SAFETY: we checked before if it's not present, and we throw an error
adapter.adapterFeatures,
logger,
);
for (const [featureName, supported] of Object.entries(validationResult)) {
// If `supported` / `validationResult[featureName]` only allows boolean,
// in theory 'assets' false, doesn't mean that the feature is not supported, but rather that the chosen image service is unsupported
// in this case we should not show an error, that the featrue is not supported
// if we would refactor the validation to support more than boolean, we could still be able to differentiate between the two cases
if (!supported && featureName !== 'assets') {
logger.error(
null,
`The adapter ${adapter.name} doesn't support the feature ${featureName}. Your project won't be built. You should not use it.`,
);
}
}
}
settings.adapter = adapter;
},

View file

@ -3,6 +3,7 @@ import type { ViteDevServer, InlineConfig as ViteInlineConfig } from 'vite';
import type { SerializedSSRManifest } from '../../core/app/types.js';
import type { PageBuildData } from '../../core/build/types.js';
import type { AstroIntegrationLogger } from '../../core/logger/core.js';
import type { AdapterFeatureStability } from '../../integrations/features-validation.js';
import type { getToolbarServerCommunicationHelpers } from '../../integrations/hooks.js';
import type { DeepPartial } from '../../type-utils.js';
import type { AstroConfig } from './config.js';
@ -60,7 +61,15 @@ export interface AstroRenderer {
serverEntrypoint: string;
}
export type AdapterSupportsKind = 'unsupported' | 'stable' | 'experimental' | 'deprecated';
export type AdapterSupportsKind =
(typeof AdapterFeatureStability)[keyof typeof AdapterFeatureStability];
export type AdapterSupportWithMessage = {
support: Exclude<AdapterSupportsKind, 'stable'>;
message: string;
};
export type AdapterSupport = AdapterSupportsKind | AdapterSupportWithMessage;
export interface AstroAdapterFeatures {
/**
@ -92,46 +101,34 @@ export type AstroAdapterFeatureMap = {
/**
* The adapter is able serve static pages
*/
staticOutput?: AdapterSupportsKind;
staticOutput?: AdapterSupport;
/**
* The adapter is able to serve pages that are static or rendered via server
*/
hybridOutput?: AdapterSupportsKind;
hybridOutput?: AdapterSupport;
/**
* The adapter is able to serve SSR pages
*/
serverOutput?: AdapterSupportsKind;
/**
* The adapter can emit static assets
*/
assets?: AstroAssetsFeature;
serverOutput?: AdapterSupport;
/**
* List of features that orbit around the i18n routing
* The adapter is able to support i18n domains
*/
i18nDomains?: AdapterSupportsKind;
i18nDomains?: AdapterSupport;
/**
* The adapter is able to support `getSecret` exported from `astro:env/server`
*/
envGetSecret?: AdapterSupportsKind;
envGetSecret?: AdapterSupport;
/**
* The adapter supports image transformation using the built-in Sharp image service
*/
sharpImageService?: AdapterSupport;
};
export interface AstroAssetsFeature {
supportKind?: AdapterSupportsKind;
/**
* Whether if this adapter deploys files in an environment that is compatible with the library `sharp`
*/
isSharpCompatible?: boolean;
}
export interface AstroInternationalizationFeature {
/**
* The adapter should be able to create the proper redirects
*/
domains?: AdapterSupportsKind;
}
/**
* IDs for different stages of JS script injection:
* - "before-hydration": Imported client-side, before the hydration script runs. Processed & resolved by Vite.

View file

@ -153,7 +153,6 @@ describe('Astro feature map', function () {
buildOutput: 'server',
config: { output: 'static' },
},
{},
defaultLogger,
);
assert.equal(result['hybridOutput'], false);
@ -167,7 +166,6 @@ describe('Astro feature map', function () {
buildOutput: 'server',
config: { output: 'static' },
},
{},
defaultLogger,
);
assert.equal(result['hybridOutput'], false);
@ -181,7 +179,6 @@ describe('Astro feature map', function () {
{
config: { output: 'static' },
},
{},
defaultLogger,
);
assert.equal(result['staticOutput'], true);
@ -195,7 +192,6 @@ describe('Astro feature map', function () {
buildOutput: 'static',
config: { output: 'static' },
},
{},
defaultLogger,
);
assert.equal(result['staticOutput'], false);
@ -209,7 +205,6 @@ describe('Astro feature map', function () {
{
config: { output: 'static' },
},
{},
defaultLogger,
);
assert.equal(result['hybridOutput'], true);
@ -225,7 +220,6 @@ describe('Astro feature map', function () {
buildOutput: 'server',
config: { output: 'static' },
},
{},
defaultLogger,
);
assert.equal(result['hybridOutput'], false);
@ -239,7 +233,6 @@ describe('Astro feature map', function () {
{
config: { output: 'server' },
},
{},
defaultLogger,
);
assert.equal(result['serverOutput'], true);
@ -254,62 +247,11 @@ describe('Astro feature map', function () {
{
config: { output: 'server' },
},
{},
defaultLogger,
);
assert.equal(result['serverOutput'], false);
});
});
describe('assets', function () {
it('should be supported when it is sharp compatible', () => {
let result = validateSupportedFeatures(
'test',
{
assets: {
supportKind: 'stable',
isSharpCompatible: true,
},
},
{
config: {
image: {
service: {
entrypoint: 'astro/assets/services/sharp',
},
},
},
},
{},
defaultLogger,
);
assert.equal(result['assets'], true);
});
it("should not be valid if the config is correct, but the it's unsupported", () => {
let result = validateSupportedFeatures(
'test',
{
assets: {
supportKind: 'unsupported',
isNodeCompatible: false,
},
},
{
config: {
image: {
service: {
entrypoint: 'astro/assets/services/sharp',
},
},
},
},
{},
defaultLogger,
);
assert.equal(result['assets'], false);
});
});
});
describe('normalizeInjectedTypeFilename', () => {