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:
parent
ee38b3a946
commit
f7f2338c2b
9 changed files with 131 additions and 168 deletions
7
.changeset/fluffy-jars-live.md
Normal file
7
.changeset/fluffy-jars-live.md
Normal 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.
|
7
.changeset/slimy-queens-hang.md
Normal file
7
.changeset/slimy-queens-hang.md
Normal 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.
|
5
.changeset/unlucky-bobcats-sit.md
Normal file
5
.changeset/unlucky-bobcats-sit.md
Normal 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.
|
|
@ -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',
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
Loading…
Reference in a new issue