Skip to content

Commit

Permalink
Merge pull request #438 from mittwald/feature/ddev-select-projecttype
Browse files Browse the repository at this point in the history
Interactively prompt for DDEV project type if undiscoverable
  • Loading branch information
LukasFritzeDev authored Apr 29, 2024
2 parents 296144e + 8f96ac6 commit dd50898
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 38 deletions.
38 changes: 25 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3264,19 +3264,23 @@ Initialize a new ddev project in the current directory.

```
USAGE
$ mw ddev init [INSTALLATION-ID] [-q] [--override-type <value>] [--without-database | --database-id <value>]
[--project-name <value>] [--override-mittwald-plugin <value>]
$ mw ddev init [INSTALLATION-ID] [-q] [--override-type
backdrop|craftcms|django4|drupal6|drupal7|drupal|laravel|magento|magento2|php|python|shopware6|silverstripe|typo3|wo
rdpress|auto] [--without-database | --database-id <value>] [--project-name <value>] [--override-mittwald-plugin
<value>]
ARGUMENTS
INSTALLATION-ID ID or short ID of an app installation; this argument is optional if a default app installation is set
in the context
FLAGS
-q, --quiet suppress process output and only display a machine-readable summary.
--database-id=<value> ID of the application database
--override-type=<value> [default: auto] Override the type of the generated DDEV configuration
--project-name=<value> DDEV project name
--without-database Create a DDEV project without a database
-q, --quiet suppress process output and only display a machine-readable summary.
--database-id=<value> ID of the application database
--override-type=<option> [default: auto] Override the type of the generated DDEV configuration
<options: backdrop|craftcms|django4|drupal6|drupal7|drupal|laravel|magento|magento2|php|
python|shopware6|silverstripe|typo3|wordpress|auto>
--project-name=<value> DDEV project name
--without-database Create a DDEV project without a database
DEVELOPMENT FLAGS
--override-mittwald-plugin=<value> [default: mittwald/ddev] override the mittwald plugin
Expand Down Expand Up @@ -3316,7 +3320,9 @@ FLAG DESCRIPTIONS
This flag allows you to override the mittwald plugin that should be installed by default; this is useful for testing
purposes
--override-type=<value> Override the type of the generated DDEV configuration
--override-type=backdrop|craftcms|django4|drupal6|drupal7|drupal|laravel|magento|magento2|php|python|shopware6|silverstripe|typo3|wordpress|auto
Override the type of the generated DDEV configuration
The type of the generated DDEV configuration; this can be any of the documented DDEV project types, or 'auto' (which
is also the default) for automatic discovery.
Expand All @@ -3339,16 +3345,20 @@ Generate a DDEV configuration YAML file for the current app.

```
USAGE
$ mw ddev render-config [INSTALLATION-ID] [--override-type <value>] [--without-database | --database-id <value>]
$ mw ddev render-config [INSTALLATION-ID] [--override-type
backdrop|craftcms|django4|drupal6|drupal7|drupal|laravel|magento|magento2|php|python|shopware6|silverstripe|typo3|wo
rdpress|auto] [--without-database | --database-id <value>]
ARGUMENTS
INSTALLATION-ID ID or short ID of an app installation; this argument is optional if a default app installation is set
in the context
FLAGS
--database-id=<value> ID of the application database
--override-type=<value> [default: auto] Override the type of the generated DDEV configuration
--without-database Create a DDEV project without a database
--database-id=<value> ID of the application database
--override-type=<option> [default: auto] Override the type of the generated DDEV configuration
<options: backdrop|craftcms|django4|drupal6|drupal7|drupal|laravel|magento|magento2|php|pyth
on|shopware6|silverstripe|typo3|wordpress|auto>
--without-database Create a DDEV project without a database
DESCRIPTION
Generate a DDEV configuration YAML file for the current app.
Expand All @@ -3364,7 +3374,9 @@ FLAG DESCRIPTIONS
Setting a database ID (either automatically or manually) is required. To create a DDEV project without a database,
set the --without-database flag.
--override-type=<value> Override the type of the generated DDEV configuration
--override-type=backdrop|craftcms|django4|drupal6|drupal7|drupal|laravel|magento|magento2|php|python|shopware6|silverstripe|typo3|wordpress|auto
Override the type of the generated DDEV configuration
The type of the generated DDEV configuration; this can be any of the documented DDEV project types, or 'auto' (which
is also the default) for automatic discovery.
Expand Down
14 changes: 13 additions & 1 deletion src/commands/ddev/init.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import {
assertDDEVIsInstalled,
determineDDEVVersion,
} from "../../lib/ddev/init_assert.js";
import {
DDEVProjectType,
determineProjectType,
} from "../../lib/ddev/init_projecttype.js";

type AppInstallation = MittwaldAPIV2.Components.Schemas.AppAppInstallation;

Expand Down Expand Up @@ -74,6 +78,12 @@ export class Init extends ExecRenderBaseCommand<typeof Init, void> {

const ddevVersion = await determineDDEVVersion(r);
const appInstallation = await this.getAppInstallation(r, appInstallationId);
const projectType = await determineProjectType(
r,
this.apiClient,
appInstallation,
this.flags["override-type"] as DDEVProjectType | "auto",
);
const databaseId = await determineDDEVDatabaseId(
r,
this.apiClient,
Expand All @@ -85,6 +95,7 @@ export class Init extends ExecRenderBaseCommand<typeof Init, void> {
r,
appInstallationId,
databaseId,
projectType,
);
const projectName = await this.determineProjectName(r);

Expand Down Expand Up @@ -213,6 +224,7 @@ export class Init extends ExecRenderBaseCommand<typeof Init, void> {
r: ProcessRenderer,
appInstallationId: string,
databaseId: string | undefined,
projectType: string,
) {
const builder = new DDEVConfigBuilder(this.apiClient);

Expand All @@ -222,7 +234,7 @@ export class Init extends ExecRenderBaseCommand<typeof Init, void> {
const config = await builder.build(
appInstallationId,
databaseId,
this.flags["override-type"],
projectType,
);
const configFile = path.join(".ddev", "config.mittwald.yaml");

Expand Down
35 changes: 11 additions & 24 deletions src/lib/ddev/config_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import {
DDEVHook,
DDEVHooks,
} from "./config.js";
import { typo3Installer } from "../../commands/app/install/typo3.js";
import { wordpressInstaller } from "../../commands/app/install/wordpress.js";
import { shopware6Installer } from "../../commands/app/install/shopware6.js";
import { drupalInstaller } from "../../commands/app/install/drupal.js";
import { determineProjectTypeFromAppInstallation } from "./init_projecttype.js";

type AppInstallation = MittwaldAPIV2.Components.Schemas.AppAppInstallation;
type AppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion;
Expand Down Expand Up @@ -102,27 +99,17 @@ export class DDEVConfigBuilder {
return type;
}

switch (inst.appId) {
case typo3Installer.appId:
return "typo3";
case wordpressInstaller.appId:
return "wordpress";
case shopware6Installer.appId:
return "shopware6";
case drupalInstaller.appId: {
const version = await this.getAppVersion(
inst.appId,
inst.appVersion.desired,
);

const [major] = version.externalVersion.split(".");
return `drupal${major}`;
}
default:
throw new Error(
"Automatic project type detection failed. Please specify the project type manually by setting the `--override-type` flag.",
);
const autoDetermined = await determineProjectTypeFromAppInstallation(
this.apiClient,
inst,
);
if (autoDetermined) {
return autoDetermined;
}

throw new Error(
"Automatic project type detection failed. Please specify the project type manually by setting the `--override-type` flag.",
);
}

private async determineDatabaseVersion(
Expand Down
2 changes: 2 additions & 0 deletions src/lib/ddev/flags.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Flags } from "@oclif/core";
import { InferredFlags } from "@oclif/core/lib/interfaces/index.js";
import { knownDDEVProjectTypes } from "./init_projecttype.js";

const ddevDatabaseFlags = {
"database-id": Flags.string({
Expand All @@ -24,6 +25,7 @@ export const ddevFlags = {
"override-type": Flags.string({
summary: "Override the type of the generated DDEV configuration",
default: "auto",
options: [...knownDDEVProjectTypes, "auto"] as const,
description:
"The type of the generated DDEV configuration; this can be any of the documented DDEV project types, or 'auto' (which is also the default) for automatic discovery." +
"\n\n" +
Expand Down
153 changes: 153 additions & 0 deletions src/lib/ddev/init_projecttype.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import type { MittwaldAPIV2 } from "@mittwald/api-client";
import { MittwaldAPIV2Client, assertStatus } from "@mittwald/api-client";
import { typo3Installer } from "../../commands/app/install/typo3.js";
import { wordpressInstaller } from "../../commands/app/install/wordpress.js";
import { shopware6Installer } from "../../commands/app/install/shopware6.js";
import { drupalInstaller } from "../../commands/app/install/drupal.js";
import { ProcessRenderer } from "../../rendering/process/process.js";
import { Value } from "../../rendering/react/components/Value.js";
import { Text } from "ink";

type AppInstallation = MittwaldAPIV2.Components.Schemas.AppAppInstallation;
type AppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion;

/**
* A list of all known DDEV project types. Shamelessly stolen from
* https://ddev.readthedocs.io/en/latest/users/configuration/config/#type
*/
export const knownDDEVProjectTypes = [
"backdrop",
"craftcms",
"django4",
"drupal6",
"drupal7",
"drupal",
"laravel",
"magento",
"magento2",
"php",
"python",
"shopware6",
"silverstripe",
"typo3",
"wordpress",
] as const;

export type DDEVProjectType = (typeof knownDDEVProjectTypes)[number];

async function getAppVersion(
client: MittwaldAPIV2Client,
appId: string,
appVersionId: string,
): Promise<AppVersion> {
const r = await client.app.getAppversion({
appId,
appVersionId,
});

assertStatus(r, 200);
return r.data;
}

/**
* Determines the DDEV project type to use for the given app installation.
*
* This is done according to the following rules:
*
* 1. If an explicit override was specified (typically using the --override-type
* flag), use that.
* 2. If the app installation is a known app type (e.g. TYPO3, WordPress, etc.),
* use the corresponding DDEV project type.
* 3. Prompt the user to interactively select a DDEV project type.
*/
export async function determineProjectType(
r: ProcessRenderer,
client: MittwaldAPIV2Client,
inst: AppInstallation,
typeOverride: DDEVProjectType | "auto",
): Promise<DDEVProjectType> {
if (typeOverride !== "auto") {
r.addInfo(<ProjectTypeInfoOverride type={typeOverride} />);
return typeOverride;
}

const determinedProjectType = await determineProjectTypeFromAppInstallation(
client,
inst,
);
if (determinedProjectType !== null) {
r.addInfo(<ProjectTypeInfoAuto type={determinedProjectType} />);
return determinedProjectType;
}

return await promptProjectTypeFromUser(r);
}

/**
* Interactively prompts the user to select a DDEV project type.
*
* The list of known project types is hardcoded in this module.
*/
async function promptProjectTypeFromUser(
r: ProcessRenderer,
): Promise<DDEVProjectType> {
return r.addSelect(
"select the DDEV project type",
knownDDEVProjectTypes.map((t) => ({ value: t, label: t })),
);
}

/**
* Determines the project type to use for the given app installation.
*
* In most cases, this is a simple mapping from known app IDs to DDEV project
* types. In some cases, we might need to know the specific (major) version of
* an app to determine the correct project type (for example, DDEV has separate
* project types for the different Drupal major versions).
*/
export async function determineProjectTypeFromAppInstallation(
client: MittwaldAPIV2Client,
inst: AppInstallation,
): Promise<DDEVProjectType | null> {
switch (inst.appId) {
case typo3Installer.appId:
return "typo3";
case wordpressInstaller.appId:
return "wordpress";
case shopware6Installer.appId:
return "shopware6";
case drupalInstaller.appId: {
const version = await getAppVersion(
client,
inst.appId,
inst.appVersion.desired,
);

const [major] = version.externalVersion.split(".");
if (major === "6" || major === "7") {
return `drupal${major}`;
}

return "drupal";
}
default:
return null;
}
}

function ProjectTypeInfoOverride({ type }: { type: DDEVProjectType }) {
return (
<Text>
using DDEV project type: <Value>{type}</Value> (explicitly specified)
</Text>
);
}

function ProjectTypeInfoAuto({ type }: { type: DDEVProjectType }) {
return (
<Text>
using DDEV project type: <Value>{type}</Value> (derived from app
installation)
</Text>
);
}

0 comments on commit dd50898

Please sign in to comment.