full site update

This commit is contained in:
2025-07-24 18:46:24 +02:00
parent bfe2b90d8d
commit 37a6e0ab31
6912 changed files with 540482 additions and 361712 deletions

View File

@@ -1,14 +1,6 @@
import { type ProxifiedModule } from 'magicast';
import { type Flags } from '../flags.js';
interface AddOptions {
flags: Flags;
}
interface IntegrationInfo {
id: string;
packageName: string;
dependencies: [name: string, version: string][];
type: 'integration' | 'adapter';
}
export declare function add(names: string[], { flags }: AddOptions): Promise<void>;
export declare function setAdapter(mod: ProxifiedModule<any>, adapter: IntegrationInfo, exportName: string): void;
export {};

View File

@@ -6,10 +6,10 @@ import { diffWords } from "diff";
import { bold, cyan, dim, green, magenta, red, yellow } from "kleur/colors";
import { builders, generateCode, loadFile } from "magicast";
import { getDefaultExportOptions } from "magicast/helpers";
import ora from "ora";
import preferredPM from "preferred-pm";
import { detect, resolveCommand } from "package-manager-detector";
import prompts from "prompts";
import maxSatisfying from "semver/ranges/max-satisfying.js";
import yoctoSpinner from "yocto-spinner";
import {
loadTSConfig,
resolveConfig,
@@ -38,15 +38,7 @@ const STUBS = {
ASTRO_CONFIG: `import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({});`,
TAILWIND_CONFIG: `/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
theme: {
extend: {},
},
plugins: [],
}
`,
TAILWIND_GLOBAL_CSS: `@import "tailwindcss";`,
SVELTE_CONFIG: `import { vitePreprocess } from '@astrojs/svelte';
export default {
@@ -73,7 +65,7 @@ export default async function seed() {
};
const OFFICIAL_ADAPTER_TO_IMPORT_MAP = {
netlify: "@astrojs/netlify",
vercel: "@astrojs/vercel/serverless",
vercel: "@astrojs/vercel",
cloudflare: "@astrojs/cloudflare",
node: "@astrojs/node"
};
@@ -125,7 +117,7 @@ async function add(names, { flags }) {
const cwd = inlineConfig.root;
const logger = createLoggerFromFlags(flags);
const integrationNames = names.map((name) => ALIASES.has(name) ? ALIASES.get(name) : name);
const integrations = await validateIntegrations(integrationNames);
const integrations = await validateIntegrations(integrationNames, flags);
let installResult = await tryToInstallIntegrations({ integrations, cwd, flags, logger });
const rootPath = resolveRoot(cwd);
const root = pathToFileURL(rootPath);
@@ -133,22 +125,30 @@ async function add(names, { flags }) {
switch (installResult) {
case 1 /* updated */: {
if (integrations.find((integration) => integration.id === "tailwind")) {
await setupIntegrationConfig({
root,
logger,
flags,
integrationName: "Tailwind",
possibleConfigFiles: [
"./tailwind.config.cjs",
"./tailwind.config.mjs",
"./tailwind.config.ts",
"./tailwind.config.mts",
"./tailwind.config.cts",
"./tailwind.config.js"
],
defaultConfigFile: "./tailwind.config.mjs",
defaultConfigContent: STUBS.TAILWIND_CONFIG
});
const dir = new URL("./styles/", new URL(userConfig.srcDir ?? "./src/", root));
const styles = new URL("./global.css", dir);
if (!existsSync(styles)) {
logger.info(
"SKIP_FORMAT",
`
${magenta(`Astro will scaffold ${green("./src/styles/global.css")}.`)}
`
);
if (await askToContinue({ flags })) {
if (!existsSync(dir)) {
await fs.mkdir(dir);
}
await fs.writeFile(styles, STUBS.TAILWIND_GLOBAL_CSS, "utf-8");
} else {
logger.info(
"SKIP_FORMAT",
`
@tailwindcss/vite requires additional configuration. Please refer to https://docs.astro.build/en/guides/integrations-guide/tailwind/`
);
}
} else {
logger.debug("add", `Using existing tailwind configuration`);
}
}
if (integrations.find((integration) => integration.id === "svelte")) {
await setupIntegrationConfig({
@@ -190,7 +190,7 @@ async function add(names, { flags }) {
logger.debug("add", `Using existing db configuration`);
}
}
if (integrations.find((integration) => integration.id === "lit") && (await preferredPM(fileURLToPath(root)))?.name === "pnpm") {
if (integrations.find((integration) => integration.id === "lit") && (await detect({ cwd: fileURLToPath(root) }))?.name === "pnpm") {
await setupIntegrationConfig({
root,
logger,
@@ -254,11 +254,13 @@ async function add(names, { flags }) {
`
${magenta(
`Check our deployment docs for ${bold(
integration.packageName
integration.integrationName
)} to update your "adapter" config.`
)}`
);
}
} else if (integration.id === "tailwind") {
addVitePlugin(mod, "tailwindcss", "@tailwindcss/vite");
} else {
addIntegration(mod, integration);
}
@@ -307,10 +309,12 @@ async function add(names, { flags }) {
logger.info("SKIP_FORMAT", msg.success(`Configuration up-to-date.`));
break;
}
// NOTE: failure shouldn't happen in practice because `updateAstroConfig` doesn't return that.
// Pipe this to the same handling as `UpdateResult.updated` for now.
case 3 /* failure */:
case 1 /* updated */:
case void 0: {
const list = integrations.map((integration) => ` - ${integration.packageName}`).join("\n");
const list = integrations.map((integration) => ` - ${integration.integrationName}`).join("\n");
logger.info(
"SKIP_FORMAT",
msg.success(
@@ -318,6 +322,21 @@ async function add(names, { flags }) {
${list}`
)
);
if (integrations.find((integration) => integration.integrationName === "tailwind")) {
const code = boxen(getDiffContent("---\n---", "---\nimport '../styles/global.css'\n---"), {
margin: 0.5,
padding: 0.5,
borderStyle: "round",
title: "src/layouts/Layout.astro"
});
logger.warn(
"SKIP_FORMAT",
msg.actionRequired(
"You must import your Tailwind stylesheet, e.g. in a shared layout:\n"
)
);
logger.info("SKIP_FORMAT", code + "\n");
}
}
}
const updateTSConfigResult = await updateTSConfig(cwd, logger, integrations, flags);
@@ -373,6 +392,23 @@ function addIntegration(mod, integration) {
config.integrations.push(builders.functionCall(integrationId));
}
}
function addVitePlugin(mod, pluginId, packageName) {
const config = getDefaultExportOptions(mod);
if (!mod.imports.$items.some((imp) => imp.local === pluginId)) {
mod.imports.$append({
imported: "default",
local: pluginId,
from: packageName
});
}
config.vite ??= {};
config.vite.plugins ??= [];
if (!config.vite.plugins.$ast.elements.some(
(el) => el.type === "CallExpression" && el.callee.type === "Identifier" && el.callee.name === pluginId
)) {
config.vite.plugins.push(builders.functionCall(pluginId));
}
}
function setAdapter(mod, adapter, exportName) {
const config = getDefaultExportOptions(mod);
const adapterId = toIdent(adapter.id);
@@ -383,9 +419,6 @@ function setAdapter(mod, adapter, exportName) {
from: exportName
});
}
if (!config.output) {
config.output = "server";
}
switch (adapter.id) {
case "node":
config.adapter = builders.functionCall(adapterId, { mode: "standalone" });
@@ -458,32 +491,9 @@ ${message}`
return 2 /* cancelled */;
}
}
async function getInstallIntegrationsCommand({
integrations,
logger,
cwd = process.cwd()
}) {
const pm = await preferredPM(cwd);
logger.debug("add", `package manager: ${JSON.stringify(pm)}`);
if (!pm) return null;
const dependencies = await convertIntegrationsToInstallSpecifiers(integrations);
switch (pm.name) {
case "npm":
return { pm: "npm", command: "install", flags: [], dependencies };
case "yarn":
return { pm: "yarn", command: "add", flags: [], dependencies };
case "pnpm":
return { pm: "pnpm", command: "add", flags: [], dependencies };
case "bun":
return { pm: "bun", command: "add", flags: [], dependencies };
default:
return null;
}
}
async function convertIntegrationsToInstallSpecifiers(integrations) {
const ranges = {};
for (let { packageName, dependencies } of integrations) {
ranges[packageName] = "*";
for (let { dependencies } of integrations) {
for (const [name, range] of dependencies) {
ranges[name] = range;
}
@@ -515,7 +525,14 @@ async function tryToInstallIntegrations({
flags,
logger
}) {
const installCommand = await getInstallIntegrationsCommand({ integrations, cwd, logger });
const packageManager = await detect({
cwd,
// Include the `install-metadata` strategy to have the package manager that's
// used for installation take precedence
strategies: ["install-metadata", "lockfile", "packageManager-field"]
});
logger.debug("add", `package manager: "${packageManager?.name}"`);
if (!packageManager) return 0 /* none */;
const inheritedFlags = Object.entries(flags).map(([flag]) => {
if (flag == "_") return;
if (INHERITED_FLAGS.has(flag)) {
@@ -523,64 +540,52 @@ async function tryToInstallIntegrations({
return `--${flag}`;
}
}).filter(Boolean).flat();
if (installCommand === null) {
return 0 /* none */;
} else {
const coloredOutput = `${bold(installCommand.pm)} ${installCommand.command}${[
"",
...installCommand.flags,
...inheritedFlags
].join(" ")} ${cyan(installCommand.dependencies.join(" "))}`;
const message = `
const installCommand = resolveCommand(packageManager?.agent ?? "npm", "add", inheritedFlags);
if (!installCommand) return 0 /* none */;
const installSpecifiers = await convertIntegrationsToInstallSpecifiers(integrations).then(
(specifiers) => installCommand.command === "deno" ? specifiers.map((specifier) => `npm:${specifier}`) : specifiers
);
const coloredOutput = `${bold(installCommand.command)} ${installCommand.args.join(" ")} ${cyan(installSpecifiers.join(" "))}`;
const message = `
${boxen(coloredOutput, {
margin: 0.5,
padding: 0.5,
borderStyle: "round"
})}
margin: 0.5,
padding: 0.5,
borderStyle: "round"
})}
`;
logger.info(
"SKIP_FORMAT",
`
logger.info(
"SKIP_FORMAT",
`
${magenta("Astro will run the following command:")}
${dim(
"If you skip this step, you can always run it yourself later"
)}
"If you skip this step, you can always run it yourself later"
)}
${message}`
);
if (await askToContinue({ flags })) {
const spinner = ora("Installing dependencies...").start();
try {
await exec(
installCommand.pm,
[
installCommand.command,
...installCommand.flags,
...inheritedFlags,
...installCommand.dependencies
],
{
nodeOptions: {
cwd,
// reset NODE_ENV to ensure install command run in dev mode
env: { NODE_ENV: void 0 }
}
}
);
spinner.succeed();
return 1 /* updated */;
} catch (err) {
spinner.fail();
logger.debug("add", "Error installing dependencies", err);
console.error("\n", err.stdout || err.message, "\n");
return 3 /* failure */;
}
} else {
return 2 /* cancelled */;
);
if (await askToContinue({ flags })) {
const spinner = yoctoSpinner({ text: "Installing dependencies..." }).start();
try {
await exec(installCommand.command, [...installCommand.args, ...installSpecifiers], {
nodeOptions: {
cwd,
// reset NODE_ENV to ensure install command run in dev mode
env: { NODE_ENV: void 0 }
}
});
spinner.success();
return 1 /* updated */;
} catch (err) {
spinner.error();
logger.debug("add", "Error installing dependencies", err);
console.error("\n", err.stdout || err.message, "\n");
return 3 /* failure */;
}
} else {
return 2 /* cancelled */;
}
}
async function validateIntegrations(integrations) {
const spinner = ora("Resolving packages...").start();
async function validateIntegrations(integrations, flags) {
const spinner = yoctoSpinner({ text: "Resolving packages..." }).start();
try {
const integrationEntries = await Promise.all(
integrations.map(async (integration) => {
@@ -597,16 +602,10 @@ async function validateIntegrations(integrations) {
const firstPartyPkgCheck = await fetchPackageJson("@astrojs", name, tag);
if (firstPartyPkgCheck instanceof Error) {
if (firstPartyPkgCheck.message) {
spinner.warn(yellow(firstPartyPkgCheck.message));
spinner.warning(yellow(firstPartyPkgCheck.message));
}
spinner.warn(yellow(`${bold(integration)} is not an official Astro package.`));
const response = await prompts({
type: "confirm",
name: "askToContinue",
message: "Continue?",
initial: true
});
if (!response.askToContinue) {
spinner.warning(yellow(`${bold(integration)} is not an official Astro package.`));
if (!await askToContinue({ flags })) {
throw new Error(
`No problem! Find our official integrations at ${cyan(
"https://astro.build/integrations"
@@ -624,7 +623,7 @@ async function validateIntegrations(integrations) {
const thirdPartyPkgCheck = await fetchPackageJson(scope, name, tag);
if (thirdPartyPkgCheck instanceof Error) {
if (thirdPartyPkgCheck.message) {
spinner.warn(yellow(thirdPartyPkgCheck.message));
spinner.warning(yellow(thirdPartyPkgCheck.message));
}
throw new Error(`Unable to fetch ${bold(integration)}. Does the package exist?`);
} else {
@@ -633,6 +632,7 @@ async function validateIntegrations(integrations) {
}
const resolvedScope = pkgType === "first-party" ? "@astrojs" : scope;
const packageName = `${resolvedScope ? `${resolvedScope}/` : ""}${name}`;
let integrationName = packageName;
let dependencies = [
[pkgJson["name"], `^${pkgJson["version"]}`]
];
@@ -661,14 +661,27 @@ async function validateIntegrations(integrations) {
)}`
);
}
return { id: integration, packageName, dependencies, type: integrationType };
if (integration === "tailwind") {
integrationName = "tailwind";
dependencies = [
["@tailwindcss/vite", "^4.0.0"],
["tailwindcss", "^4.0.0"]
];
}
return {
id: integration,
packageName,
dependencies,
type: integrationType,
integrationName
};
})
);
spinner.succeed();
spinner.success();
return integrationEntries;
} catch (e) {
if (e instanceof Error) {
spinner.fail(e.message);
spinner.error(e.message);
process.exit(1);
} else {
throw e;
@@ -828,6 +841,5 @@ async function setupIntegrationConfig(opts) {
}
}
export {
add,
setAdapter
add
};