Refactor routing in App component to enhance navigation and improve error handling by integrating dynamic routes and updating the NotFound route.
This commit is contained in:
14
node_modules/astro/dist/cli/add/index.d.ts
generated
vendored
Normal file
14
node_modules/astro/dist/cli/add/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
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 {};
|
833
node_modules/astro/dist/cli/add/index.js
generated
vendored
Normal file
833
node_modules/astro/dist/cli/add/index.js
generated
vendored
Normal file
@@ -0,0 +1,833 @@
|
||||
import fsMod, { existsSync, promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import boxen from "boxen";
|
||||
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 prompts from "prompts";
|
||||
import maxSatisfying from "semver/ranges/max-satisfying.js";
|
||||
import {
|
||||
loadTSConfig,
|
||||
resolveConfig,
|
||||
resolveConfigPath,
|
||||
resolveRoot
|
||||
} from "../../core/config/index.js";
|
||||
import {
|
||||
defaultTSConfig,
|
||||
presets,
|
||||
updateTSConfigForFramework
|
||||
} from "../../core/config/tsconfig.js";
|
||||
import * as msg from "../../core/messages.js";
|
||||
import { printHelp } from "../../core/messages.js";
|
||||
import { appendForwardSlash } from "../../core/path.js";
|
||||
import { apply as applyPolyfill } from "../../core/polyfill.js";
|
||||
import { ensureProcessNodeEnv, parseNpmName } from "../../core/util.js";
|
||||
import { eventCliSession, telemetry } from "../../events/index.js";
|
||||
import { exec } from "../exec.js";
|
||||
import { createLoggerFromFlags, flagsToAstroInlineConfig } from "../flags.js";
|
||||
import { fetchPackageJson, fetchPackageVersions } from "../install-package.js";
|
||||
const ALIASES = /* @__PURE__ */ new Map([
|
||||
["solid", "solid-js"],
|
||||
["tailwindcss", "tailwind"]
|
||||
]);
|
||||
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: [],
|
||||
}
|
||||
`,
|
||||
SVELTE_CONFIG: `import { vitePreprocess } from '@astrojs/svelte';
|
||||
|
||||
export default {
|
||||
preprocess: vitePreprocess(),
|
||||
}
|
||||
`,
|
||||
LIT_NPMRC: `# Lit libraries are required to be hoisted due to dependency issues.
|
||||
public-hoist-pattern[]=*lit*
|
||||
`,
|
||||
DB_CONFIG: `import { defineDb } from 'astro:db';
|
||||
|
||||
// https://astro.build/db/config
|
||||
export default defineDb({
|
||||
tables: {}
|
||||
});
|
||||
`,
|
||||
DB_SEED: `import { db } from 'astro:db';
|
||||
|
||||
// https://astro.build/db/seed
|
||||
export default async function seed() {
|
||||
// TODO
|
||||
}
|
||||
`
|
||||
};
|
||||
const OFFICIAL_ADAPTER_TO_IMPORT_MAP = {
|
||||
netlify: "@astrojs/netlify",
|
||||
vercel: "@astrojs/vercel/serverless",
|
||||
cloudflare: "@astrojs/cloudflare",
|
||||
node: "@astrojs/node"
|
||||
};
|
||||
async function add(names, { flags }) {
|
||||
ensureProcessNodeEnv("production");
|
||||
applyPolyfill();
|
||||
const inlineConfig = flagsToAstroInlineConfig(flags);
|
||||
const { userConfig } = await resolveConfig(inlineConfig, "add");
|
||||
telemetry.record(eventCliSession("add", userConfig));
|
||||
if (flags.help || names.length === 0) {
|
||||
printHelp({
|
||||
commandName: "astro add",
|
||||
usage: "[...integrations] [...adapters]",
|
||||
tables: {
|
||||
Flags: [
|
||||
["--yes", "Accept all prompts."],
|
||||
["--help", "Show this help message."]
|
||||
],
|
||||
"UI Frameworks": [
|
||||
["react", "astro add react"],
|
||||
["preact", "astro add preact"],
|
||||
["vue", "astro add vue"],
|
||||
["svelte", "astro add svelte"],
|
||||
["solid-js", "astro add solid-js"],
|
||||
["lit", "astro add lit"],
|
||||
["alpinejs", "astro add alpinejs"]
|
||||
],
|
||||
"Documentation Frameworks": [["starlight", "astro add starlight"]],
|
||||
"SSR Adapters": [
|
||||
["netlify", "astro add netlify"],
|
||||
["vercel", "astro add vercel"],
|
||||
["deno", "astro add deno"],
|
||||
["cloudflare", "astro add cloudflare"],
|
||||
["node", "astro add node"]
|
||||
],
|
||||
Others: [
|
||||
["db", "astro add db"],
|
||||
["tailwind", "astro add tailwind"],
|
||||
["mdx", "astro add mdx"],
|
||||
["markdoc", "astro add markdoc"],
|
||||
["partytown", "astro add partytown"],
|
||||
["sitemap", "astro add sitemap"]
|
||||
]
|
||||
},
|
||||
description: `For more integrations, check out: ${cyan("https://astro.build/integrations")}`
|
||||
});
|
||||
return;
|
||||
}
|
||||
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);
|
||||
let installResult = await tryToInstallIntegrations({ integrations, cwd, flags, logger });
|
||||
const rootPath = resolveRoot(cwd);
|
||||
const root = pathToFileURL(rootPath);
|
||||
root.href = appendForwardSlash(root.href);
|
||||
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
|
||||
});
|
||||
}
|
||||
if (integrations.find((integration) => integration.id === "svelte")) {
|
||||
await setupIntegrationConfig({
|
||||
root,
|
||||
logger,
|
||||
flags,
|
||||
integrationName: "Svelte",
|
||||
possibleConfigFiles: ["./svelte.config.js", "./svelte.config.cjs", "./svelte.config.mjs"],
|
||||
defaultConfigFile: "./svelte.config.js",
|
||||
defaultConfigContent: STUBS.SVELTE_CONFIG
|
||||
});
|
||||
}
|
||||
if (integrations.find((integration) => integration.id === "db")) {
|
||||
if (!existsSync(new URL("./db/", root))) {
|
||||
logger.info(
|
||||
"SKIP_FORMAT",
|
||||
`
|
||||
${magenta(
|
||||
`Astro will scaffold ${green("./db/config.ts")}${magenta(" and ")}${green(
|
||||
"./db/seed.ts"
|
||||
)}${magenta(" files.")}`
|
||||
)}
|
||||
`
|
||||
);
|
||||
if (await askToContinue({ flags })) {
|
||||
await fs.mkdir(new URL("./db", root));
|
||||
await Promise.all([
|
||||
fs.writeFile(new URL("./db/config.ts", root), STUBS.DB_CONFIG, { encoding: "utf-8" }),
|
||||
fs.writeFile(new URL("./db/seed.ts", root), STUBS.DB_SEED, { encoding: "utf-8" })
|
||||
]);
|
||||
} else {
|
||||
logger.info(
|
||||
"SKIP_FORMAT",
|
||||
`
|
||||
Astro DB requires additional configuration. Please refer to https://astro.build/db/config`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
logger.debug("add", `Using existing db configuration`);
|
||||
}
|
||||
}
|
||||
if (integrations.find((integration) => integration.id === "lit") && (await preferredPM(fileURLToPath(root)))?.name === "pnpm") {
|
||||
await setupIntegrationConfig({
|
||||
root,
|
||||
logger,
|
||||
flags,
|
||||
integrationName: "Lit",
|
||||
possibleConfigFiles: ["./.npmrc"],
|
||||
defaultConfigFile: "./.npmrc",
|
||||
defaultConfigContent: STUBS.LIT_NPMRC
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2 /* cancelled */: {
|
||||
logger.info(
|
||||
"SKIP_FORMAT",
|
||||
msg.cancelled(
|
||||
`Dependencies ${bold("NOT")} installed.`,
|
||||
`Be sure to install them manually before continuing!`
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 3 /* failure */: {
|
||||
throw createPrettyError(new Error(`Unable to install dependencies`));
|
||||
}
|
||||
case 0 /* none */:
|
||||
break;
|
||||
}
|
||||
const rawConfigPath = await resolveConfigPath({
|
||||
root: rootPath,
|
||||
configFile: inlineConfig.configFile,
|
||||
fs: fsMod
|
||||
});
|
||||
let configURL = rawConfigPath ? pathToFileURL(rawConfigPath) : void 0;
|
||||
if (configURL) {
|
||||
logger.debug("add", `Found config at ${configURL}`);
|
||||
} else {
|
||||
logger.info("add", `Unable to locate a config file, generating one for you.`);
|
||||
configURL = new URL("./astro.config.mjs", root);
|
||||
await fs.writeFile(fileURLToPath(configURL), STUBS.ASTRO_CONFIG, { encoding: "utf-8" });
|
||||
}
|
||||
let mod;
|
||||
try {
|
||||
mod = await loadFile(fileURLToPath(configURL));
|
||||
logger.debug("add", "Parsed astro config");
|
||||
if (mod.exports.default.$type !== "function-call") {
|
||||
mod.imports.$prepend({ imported: "defineConfig", from: "astro/config" });
|
||||
mod.exports.default = builders.functionCall("defineConfig", mod.exports.default);
|
||||
} else if (mod.exports.default.$args[0] == null) {
|
||||
mod.exports.default.$args[0] = {};
|
||||
}
|
||||
logger.debug("add", "Astro config ensured `defineConfig`");
|
||||
for (const integration of integrations) {
|
||||
if (isAdapter(integration)) {
|
||||
const officialExportName = OFFICIAL_ADAPTER_TO_IMPORT_MAP[integration.id];
|
||||
if (officialExportName) {
|
||||
setAdapter(mod, integration, officialExportName);
|
||||
} else {
|
||||
logger.info(
|
||||
"SKIP_FORMAT",
|
||||
`
|
||||
${magenta(
|
||||
`Check our deployment docs for ${bold(
|
||||
integration.packageName
|
||||
)} to update your "adapter" config.`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
addIntegration(mod, integration);
|
||||
}
|
||||
logger.debug("add", `Astro config added integration ${integration.id}`);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.debug("add", "Error parsing/modifying astro config: ", err);
|
||||
throw createPrettyError(err);
|
||||
}
|
||||
let configResult;
|
||||
if (mod) {
|
||||
try {
|
||||
configResult = await updateAstroConfig({
|
||||
configURL,
|
||||
mod,
|
||||
flags,
|
||||
logger,
|
||||
logAdapterInstructions: integrations.some(isAdapter)
|
||||
});
|
||||
} catch (err) {
|
||||
logger.debug("add", "Error updating astro config", err);
|
||||
throw createPrettyError(err);
|
||||
}
|
||||
}
|
||||
switch (configResult) {
|
||||
case 2 /* cancelled */: {
|
||||
logger.info(
|
||||
"SKIP_FORMAT",
|
||||
msg.cancelled(`Your configuration has ${bold("NOT")} been updated.`)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 0 /* none */: {
|
||||
const pkgURL = new URL("./package.json", configURL);
|
||||
if (existsSync(fileURLToPath(pkgURL))) {
|
||||
const { dependencies = {}, devDependencies = {} } = await fs.readFile(fileURLToPath(pkgURL)).then((res) => JSON.parse(res.toString()));
|
||||
const deps = Object.keys(Object.assign(dependencies, devDependencies));
|
||||
const missingDeps = integrations.filter(
|
||||
(integration) => !deps.includes(integration.packageName)
|
||||
);
|
||||
if (missingDeps.length === 0) {
|
||||
logger.info("SKIP_FORMAT", msg.success(`Configuration up-to-date.`));
|
||||
break;
|
||||
}
|
||||
}
|
||||
logger.info("SKIP_FORMAT", msg.success(`Configuration up-to-date.`));
|
||||
break;
|
||||
}
|
||||
case 3 /* failure */:
|
||||
case 1 /* updated */:
|
||||
case void 0: {
|
||||
const list = integrations.map((integration) => ` - ${integration.packageName}`).join("\n");
|
||||
logger.info(
|
||||
"SKIP_FORMAT",
|
||||
msg.success(
|
||||
`Added the following integration${integrations.length === 1 ? "" : "s"} to your project:
|
||||
${list}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
const updateTSConfigResult = await updateTSConfig(cwd, logger, integrations, flags);
|
||||
switch (updateTSConfigResult) {
|
||||
case 0 /* none */: {
|
||||
break;
|
||||
}
|
||||
case 2 /* cancelled */: {
|
||||
logger.info(
|
||||
"SKIP_FORMAT",
|
||||
msg.cancelled(`Your TypeScript configuration has ${bold("NOT")} been updated.`)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 3 /* failure */: {
|
||||
throw new Error(
|
||||
`Unknown error parsing tsconfig.json or jsconfig.json. Could not update TypeScript settings.`
|
||||
);
|
||||
}
|
||||
case 1 /* updated */:
|
||||
logger.info("SKIP_FORMAT", msg.success(`Successfully updated TypeScript settings`));
|
||||
}
|
||||
}
|
||||
function isAdapter(integration) {
|
||||
return integration.type === "adapter";
|
||||
}
|
||||
const toIdent = (name) => {
|
||||
const ident = name.trim().replace(/[-_./]?astro(?:js)?[-_.]?/g, "").replace(/\.js/, "").replace(/[.\-_/]+([a-zA-Z])/g, (_, w) => w.toUpperCase()).replace(/^[^a-zA-Z$_]+/, "").replace(/@.*$/, "");
|
||||
return `${ident[0].toLowerCase()}${ident.slice(1)}`;
|
||||
};
|
||||
function createPrettyError(err) {
|
||||
err.message = `Astro could not update your astro.config.js file safely.
|
||||
Reason: ${err.message}
|
||||
|
||||
You will need to add these integration(s) manually.
|
||||
Documentation: https://docs.astro.build/en/guides/integrations-guide/`;
|
||||
return err;
|
||||
}
|
||||
function addIntegration(mod, integration) {
|
||||
const config = getDefaultExportOptions(mod);
|
||||
const integrationId = toIdent(integration.id);
|
||||
if (!mod.imports.$items.some((imp) => imp.local === integrationId)) {
|
||||
mod.imports.$append({
|
||||
imported: "default",
|
||||
local: integrationId,
|
||||
from: integration.packageName
|
||||
});
|
||||
}
|
||||
config.integrations ??= [];
|
||||
if (!config.integrations.$ast.elements.some(
|
||||
(el) => el.type === "CallExpression" && el.callee.type === "Identifier" && el.callee.name === integrationId
|
||||
)) {
|
||||
config.integrations.push(builders.functionCall(integrationId));
|
||||
}
|
||||
}
|
||||
function setAdapter(mod, adapter, exportName) {
|
||||
const config = getDefaultExportOptions(mod);
|
||||
const adapterId = toIdent(adapter.id);
|
||||
if (!mod.imports.$items.some((imp) => imp.local === adapterId)) {
|
||||
mod.imports.$append({
|
||||
imported: "default",
|
||||
local: adapterId,
|
||||
from: exportName
|
||||
});
|
||||
}
|
||||
if (!config.output) {
|
||||
config.output = "server";
|
||||
}
|
||||
switch (adapter.id) {
|
||||
case "node":
|
||||
config.adapter = builders.functionCall(adapterId, { mode: "standalone" });
|
||||
break;
|
||||
default:
|
||||
config.adapter = builders.functionCall(adapterId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
var UpdateResult = /* @__PURE__ */ ((UpdateResult2) => {
|
||||
UpdateResult2[UpdateResult2["none"] = 0] = "none";
|
||||
UpdateResult2[UpdateResult2["updated"] = 1] = "updated";
|
||||
UpdateResult2[UpdateResult2["cancelled"] = 2] = "cancelled";
|
||||
UpdateResult2[UpdateResult2["failure"] = 3] = "failure";
|
||||
return UpdateResult2;
|
||||
})(UpdateResult || {});
|
||||
async function updateAstroConfig({
|
||||
configURL,
|
||||
mod,
|
||||
flags,
|
||||
logger,
|
||||
logAdapterInstructions
|
||||
}) {
|
||||
const input = await fs.readFile(fileURLToPath(configURL), { encoding: "utf-8" });
|
||||
const output = generateCode(mod, {
|
||||
format: {
|
||||
objectCurlySpacing: true,
|
||||
useTabs: false,
|
||||
tabWidth: 2
|
||||
}
|
||||
}).code;
|
||||
if (input === output) {
|
||||
return 0 /* none */;
|
||||
}
|
||||
const diff = getDiffContent(input, output);
|
||||
if (!diff) {
|
||||
return 0 /* none */;
|
||||
}
|
||||
const message = `
|
||||
${boxen(diff, {
|
||||
margin: 0.5,
|
||||
padding: 0.5,
|
||||
borderStyle: "round",
|
||||
title: configURL.pathname.split("/").pop()
|
||||
})}
|
||||
`;
|
||||
logger.info(
|
||||
"SKIP_FORMAT",
|
||||
`
|
||||
${magenta("Astro will make the following changes to your config file:")}
|
||||
${message}`
|
||||
);
|
||||
if (logAdapterInstructions) {
|
||||
logger.info(
|
||||
"SKIP_FORMAT",
|
||||
magenta(
|
||||
` For complete deployment options, visit
|
||||
${bold(
|
||||
"https://docs.astro.build/en/guides/deploy/"
|
||||
)}
|
||||
`
|
||||
)
|
||||
);
|
||||
}
|
||||
if (await askToContinue({ flags })) {
|
||||
await fs.writeFile(fileURLToPath(configURL), output, { encoding: "utf-8" });
|
||||
logger.debug("add", `Updated astro config`);
|
||||
return 1 /* updated */;
|
||||
} else {
|
||||
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 (const [name, range] of dependencies) {
|
||||
ranges[name] = range;
|
||||
}
|
||||
}
|
||||
return Promise.all(
|
||||
Object.entries(ranges).map(([name, range]) => resolveRangeToInstallSpecifier(name, range))
|
||||
);
|
||||
}
|
||||
async function resolveRangeToInstallSpecifier(name, range) {
|
||||
const versions = await fetchPackageVersions(name);
|
||||
if (versions instanceof Error) return name;
|
||||
const stableVersions = versions.filter((v) => !v.includes("-"));
|
||||
const maxStable = maxSatisfying(stableVersions, range) ?? maxSatisfying(versions, range);
|
||||
if (!maxStable) return name;
|
||||
return `${name}@^${maxStable}`;
|
||||
}
|
||||
const INHERITED_FLAGS = /* @__PURE__ */ new Set([
|
||||
"P",
|
||||
"save-prod",
|
||||
"D",
|
||||
"save-dev",
|
||||
"E",
|
||||
"save-exact",
|
||||
"no-save"
|
||||
]);
|
||||
async function tryToInstallIntegrations({
|
||||
integrations,
|
||||
cwd,
|
||||
flags,
|
||||
logger
|
||||
}) {
|
||||
const installCommand = await getInstallIntegrationsCommand({ integrations, cwd, logger });
|
||||
const inheritedFlags = Object.entries(flags).map(([flag]) => {
|
||||
if (flag == "_") return;
|
||||
if (INHERITED_FLAGS.has(flag)) {
|
||||
if (flag.length === 1) return `-${flag}`;
|
||||
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 = `
|
||||
${boxen(coloredOutput, {
|
||||
margin: 0.5,
|
||||
padding: 0.5,
|
||||
borderStyle: "round"
|
||||
})}
|
||||
`;
|
||||
logger.info(
|
||||
"SKIP_FORMAT",
|
||||
`
|
||||
${magenta("Astro will run the following command:")}
|
||||
${dim(
|
||||
"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 */;
|
||||
}
|
||||
}
|
||||
}
|
||||
async function validateIntegrations(integrations) {
|
||||
const spinner = ora("Resolving packages...").start();
|
||||
try {
|
||||
const integrationEntries = await Promise.all(
|
||||
integrations.map(async (integration) => {
|
||||
const parsed = parseIntegrationName(integration);
|
||||
if (!parsed) {
|
||||
throw new Error(`${bold(integration)} does not appear to be a valid package name!`);
|
||||
}
|
||||
let { scope, name, tag } = parsed;
|
||||
let pkgJson;
|
||||
let pkgType;
|
||||
if (scope && scope !== "@astrojs") {
|
||||
pkgType = "third-party";
|
||||
} else {
|
||||
const firstPartyPkgCheck = await fetchPackageJson("@astrojs", name, tag);
|
||||
if (firstPartyPkgCheck instanceof Error) {
|
||||
if (firstPartyPkgCheck.message) {
|
||||
spinner.warn(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) {
|
||||
throw new Error(
|
||||
`No problem! Find our official integrations at ${cyan(
|
||||
"https://astro.build/integrations"
|
||||
)}`
|
||||
);
|
||||
}
|
||||
spinner.start("Resolving with third party packages...");
|
||||
pkgType = "third-party";
|
||||
} else {
|
||||
pkgType = "first-party";
|
||||
pkgJson = firstPartyPkgCheck;
|
||||
}
|
||||
}
|
||||
if (pkgType === "third-party") {
|
||||
const thirdPartyPkgCheck = await fetchPackageJson(scope, name, tag);
|
||||
if (thirdPartyPkgCheck instanceof Error) {
|
||||
if (thirdPartyPkgCheck.message) {
|
||||
spinner.warn(yellow(thirdPartyPkgCheck.message));
|
||||
}
|
||||
throw new Error(`Unable to fetch ${bold(integration)}. Does the package exist?`);
|
||||
} else {
|
||||
pkgJson = thirdPartyPkgCheck;
|
||||
}
|
||||
}
|
||||
const resolvedScope = pkgType === "first-party" ? "@astrojs" : scope;
|
||||
const packageName = `${resolvedScope ? `${resolvedScope}/` : ""}${name}`;
|
||||
let dependencies = [
|
||||
[pkgJson["name"], `^${pkgJson["version"]}`]
|
||||
];
|
||||
if (pkgJson["peerDependencies"]) {
|
||||
const meta = pkgJson["peerDependenciesMeta"] || {};
|
||||
for (const peer in pkgJson["peerDependencies"]) {
|
||||
const optional = meta[peer]?.optional || false;
|
||||
const isAstro = peer === "astro";
|
||||
if (!optional && !isAstro) {
|
||||
dependencies.push([peer, pkgJson["peerDependencies"][peer]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
let integrationType;
|
||||
const keywords = Array.isArray(pkgJson["keywords"]) ? pkgJson["keywords"] : [];
|
||||
if (keywords.includes("astro-integration")) {
|
||||
integrationType = "integration";
|
||||
} else if (keywords.includes("astro-adapter")) {
|
||||
integrationType = "adapter";
|
||||
} else {
|
||||
throw new Error(
|
||||
`${bold(
|
||||
packageName
|
||||
)} doesn't appear to be an integration or an adapter. Find our official integrations at ${cyan(
|
||||
"https://astro.build/integrations"
|
||||
)}`
|
||||
);
|
||||
}
|
||||
return { id: integration, packageName, dependencies, type: integrationType };
|
||||
})
|
||||
);
|
||||
spinner.succeed();
|
||||
return integrationEntries;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
spinner.fail(e.message);
|
||||
process.exit(1);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
async function updateTSConfig(cwd = process.cwd(), logger, integrationsInfo, flags) {
|
||||
const integrations = integrationsInfo.map(
|
||||
(integration) => integration.id
|
||||
);
|
||||
const firstIntegrationWithTSSettings = integrations.find(
|
||||
(integration) => presets.has(integration)
|
||||
);
|
||||
if (!firstIntegrationWithTSSettings) {
|
||||
return 0 /* none */;
|
||||
}
|
||||
let inputConfig = await loadTSConfig(cwd);
|
||||
let inputConfigText = "";
|
||||
if (inputConfig === "invalid-config" || inputConfig === "unknown-error") {
|
||||
return 3 /* failure */;
|
||||
} else if (inputConfig === "missing-config") {
|
||||
logger.debug("add", "Couldn't find tsconfig.json or jsconfig.json, generating one");
|
||||
inputConfig = {
|
||||
tsconfig: defaultTSConfig,
|
||||
tsconfigFile: path.join(cwd, "tsconfig.json"),
|
||||
rawConfig: defaultTSConfig
|
||||
};
|
||||
} else {
|
||||
inputConfigText = JSON.stringify(inputConfig.rawConfig, null, 2);
|
||||
}
|
||||
const configFileName = path.basename(inputConfig.tsconfigFile);
|
||||
const outputConfig = updateTSConfigForFramework(
|
||||
inputConfig.rawConfig,
|
||||
firstIntegrationWithTSSettings
|
||||
);
|
||||
const output = JSON.stringify(outputConfig, null, 2);
|
||||
const diff = getDiffContent(inputConfigText, output);
|
||||
if (!diff) {
|
||||
return 0 /* none */;
|
||||
}
|
||||
const message = `
|
||||
${boxen(diff, {
|
||||
margin: 0.5,
|
||||
padding: 0.5,
|
||||
borderStyle: "round",
|
||||
title: configFileName
|
||||
})}
|
||||
`;
|
||||
logger.info(
|
||||
"SKIP_FORMAT",
|
||||
`
|
||||
${magenta(`Astro will make the following changes to your ${configFileName}:`)}
|
||||
${message}`
|
||||
);
|
||||
const conflictingIntegrations = [...Object.keys(presets).filter((config) => config !== "vue")];
|
||||
const hasConflictingIntegrations = integrations.filter((integration) => presets.has(integration)).length > 1 && integrations.filter((integration) => conflictingIntegrations.includes(integration)).length > 0;
|
||||
if (hasConflictingIntegrations) {
|
||||
logger.info(
|
||||
"SKIP_FORMAT",
|
||||
red(
|
||||
` ${bold(
|
||||
"Caution:"
|
||||
)} Selected UI frameworks require conflicting tsconfig.json settings, as such only settings for ${bold(
|
||||
firstIntegrationWithTSSettings
|
||||
)} were used.
|
||||
More information: https://docs.astro.build/en/guides/typescript/#errors-typing-multiple-jsx-frameworks-at-the-same-time
|
||||
`
|
||||
)
|
||||
);
|
||||
}
|
||||
if (await askToContinue({ flags })) {
|
||||
await fs.writeFile(inputConfig.tsconfigFile, output, {
|
||||
encoding: "utf-8"
|
||||
});
|
||||
logger.debug("add", `Updated ${configFileName} file`);
|
||||
return 1 /* updated */;
|
||||
} else {
|
||||
return 2 /* cancelled */;
|
||||
}
|
||||
}
|
||||
function parseIntegrationName(spec) {
|
||||
const result = parseNpmName(spec);
|
||||
if (!result) return;
|
||||
let { scope, name } = result;
|
||||
let tag = "latest";
|
||||
if (scope) {
|
||||
name = name.replace(scope + "/", "");
|
||||
}
|
||||
if (name.includes("@")) {
|
||||
const tagged = name.split("@");
|
||||
name = tagged[0];
|
||||
tag = tagged[1];
|
||||
}
|
||||
return { scope, name, tag };
|
||||
}
|
||||
async function askToContinue({ flags }) {
|
||||
if (flags.yes || flags.y) return true;
|
||||
const response = await prompts({
|
||||
type: "confirm",
|
||||
name: "askToContinue",
|
||||
message: "Continue?",
|
||||
initial: true
|
||||
});
|
||||
return Boolean(response.askToContinue);
|
||||
}
|
||||
function getDiffContent(input, output) {
|
||||
let changes = [];
|
||||
for (const change of diffWords(input, output)) {
|
||||
let lines = change.value.trim().split("\n").slice(0, change.count);
|
||||
if (lines.length === 0) continue;
|
||||
if (change.added) {
|
||||
if (!change.value.trim()) continue;
|
||||
changes.push(change.value);
|
||||
}
|
||||
}
|
||||
if (changes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
let diffed = output;
|
||||
for (let newContent of changes) {
|
||||
const coloredOutput = newContent.split("\n").map((ln) => ln ? green(ln) : "").join("\n");
|
||||
diffed = diffed.replace(newContent, coloredOutput);
|
||||
}
|
||||
return diffed;
|
||||
}
|
||||
async function setupIntegrationConfig(opts) {
|
||||
const logger = opts.logger;
|
||||
const possibleConfigFiles = opts.possibleConfigFiles.map(
|
||||
(p) => fileURLToPath(new URL(p, opts.root))
|
||||
);
|
||||
let alreadyConfigured = false;
|
||||
for (const possibleConfigPath of possibleConfigFiles) {
|
||||
if (existsSync(possibleConfigPath)) {
|
||||
alreadyConfigured = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!alreadyConfigured) {
|
||||
logger.info(
|
||||
"SKIP_FORMAT",
|
||||
`
|
||||
${magenta(`Astro will generate a minimal ${bold(opts.defaultConfigFile)} file.`)}
|
||||
`
|
||||
);
|
||||
if (await askToContinue({ flags: opts.flags })) {
|
||||
await fs.writeFile(
|
||||
fileURLToPath(new URL(opts.defaultConfigFile, opts.root)),
|
||||
opts.defaultConfigContent,
|
||||
{
|
||||
encoding: "utf-8"
|
||||
}
|
||||
);
|
||||
logger.debug("add", `Generated default ${opts.defaultConfigFile} file`);
|
||||
}
|
||||
} else {
|
||||
logger.debug("add", `Using existing ${opts.integrationName} configuration`);
|
||||
}
|
||||
}
|
||||
export {
|
||||
add,
|
||||
setAdapter
|
||||
};
|
Reference in New Issue
Block a user