feat: add Drupal application module
This commit is contained in:
@@ -39,6 +39,18 @@ WordPress provisioning resides in `packages/aurora-module-wordpress`, including:
|
|||||||
|
|
||||||
Core contains no `type === 'wordpress'` or `moduleId === 'wordpress'` behavior.
|
Core contains no `type === 'wordpress'` or `moduleId === 'wordpress'` behavior.
|
||||||
|
|
||||||
|
## Drupal module
|
||||||
|
|
||||||
|
Drupal provisioning resides in `packages/aurora-module-drupal`, including:
|
||||||
|
|
||||||
|
- Drupal 11's Composer-recommended `web/` document-root layout
|
||||||
|
- PHP 8.4 and 8.3 compatibility constraints enforced in both the wizard and Core
|
||||||
|
- MariaDB, MySQL, and PostgreSQL installation through Drush
|
||||||
|
- Standard, Minimal, and Umami installation profiles
|
||||||
|
- Credential persistence and Application Admin integration
|
||||||
|
- A copyable **Rebuild Drupal cache** project tool
|
||||||
|
- Drupal-required DOM, cURL, GD, PDO, OPcache, XML, and multilingual runtime support
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
Commands completed successfully:
|
Commands completed successfully:
|
||||||
@@ -51,7 +63,7 @@ npm run build:modules
|
|||||||
npx electron-builder --linux AppImage
|
npx electron-builder --linux AppImage
|
||||||
```
|
```
|
||||||
|
|
||||||
Automated result: 6 test files and 27 tests passed. Coverage includes:
|
Automated result: 6 test files and 28 tests passed. Coverage includes:
|
||||||
|
|
||||||
- Empty registry
|
- Empty registry
|
||||||
- Available local packages
|
- Available local packages
|
||||||
@@ -66,6 +78,7 @@ Automated result: 6 test files and 27 tests passed. Coverage includes:
|
|||||||
- Application appearing after install and disappearing after uninstall
|
- Application appearing after install and disappearing after uninstall
|
||||||
- Existing-project missing-module state
|
- Existing-project missing-module state
|
||||||
- Validation of the independently packaged WordPress contract
|
- Validation of the independently packaged WordPress contract
|
||||||
|
- Validation and `.pac` installation of the independently packaged Drupal contract
|
||||||
- Production compilation of the lifecycle IPC, preload, and renderer tool-action path
|
- Production compilation of the lifecycle IPC, preload, and renderer tool-action path
|
||||||
- Generation of the embedded WordPress `.pac` catalog with the lifecycle-aware author tooling present
|
- Generation of the embedded WordPress `.pac` catalog with the lifecycle-aware author tooling present
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Aurora Drupal module
|
||||||
|
|
||||||
|
Installs Drupal 11 from `drupal/recommended-project`, adds Drush, and performs an unattended site installation against the Aurora project database.
|
||||||
|
|
||||||
|
The project uses Drupal's recommended `web/` document root. The project toolbar provides a cache-rebuild action through Drush.
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
function containerUser() {
|
||||||
|
if (typeof process.getuid !== 'function') return []
|
||||||
|
return ['--user', `${process.getuid()}:${process.getgid()}`]
|
||||||
|
}
|
||||||
|
|
||||||
|
function compose(context, ...args) {
|
||||||
|
return ['compose', '-f', `${context.directory}/.aurora/compose.yaml`, ...args]
|
||||||
|
}
|
||||||
|
|
||||||
|
function phpExec(context, ...args) {
|
||||||
|
return compose(context, 'exec', '-T', ...containerUser(), '-e', 'HOME=/tmp', '-e', 'COMPOSER_HOME=/tmp/composer', 'php', ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
function databaseUrl(context) {
|
||||||
|
const driver = context.environment.database === 'postgres' ? 'pgsql' : 'mysql'
|
||||||
|
return `${driver}://db:db@db:3306/db`.replace(':3306/', context.environment.database === 'postgres' ? ':5432/' : ':3306/')
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.projectCreate = async function projectCreate(context) {
|
||||||
|
const settings = context.settings
|
||||||
|
const siteName = String(settings.site_name || '').trim() || context.projectName
|
||||||
|
const temporaryProject = '/tmp/aurora-drupal-project'
|
||||||
|
await context.ensureRouter()
|
||||||
|
await context.run('Start project services', 'docker', compose(context, 'up', '-d', '--build', '--remove-orphans'))
|
||||||
|
await context.run('Download Drupal 11', 'docker', phpExec(context, 'sh', '-lc', `rm -rf ${temporaryProject} && composer create-project drupal/recommended-project:^11 ${temporaryProject} --no-interaction --no-progress && cp -a ${temporaryProject}/. /var/www/html/ && rm -rf ${temporaryProject}`))
|
||||||
|
await context.run('Install Drush', 'docker', phpExec(context, 'composer', 'require', 'drush/drush:^13', '--no-interaction', '--no-progress'))
|
||||||
|
await context.run('Install Drupal', 'docker', phpExec(context, 'vendor/bin/drush', 'site:install', String(settings.profile || 'standard'), `--db-url=${databaseUrl(context)}`, `--site-name=${siteName}`, `--account-name=${settings.admin_user}`, `--account-pass=${settings.admin_password}`, `--account-mail=${settings.admin_email}`, '--yes'))
|
||||||
|
await context.run('Prepare writable files', 'docker', phpExec(context, 'sh', '-lc', 'mkdir -p web/sites/default/files && chmod 0777 web/sites/default/files'))
|
||||||
|
await context.run('Verify Drupal', 'docker', phpExec(context, 'vendor/bin/drush', 'status', '--field=drupal-version'))
|
||||||
|
await context.setProjectMetadata({ drupalVersion: 'Drupal 11' })
|
||||||
|
await context.saveCredentials({ platform: context.moduleId, adminUrl: `${context.urls.https}/user/login`, username: String(settings.admin_user), password: String(settings.admin_password), email: String(settings.admin_email) })
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.projectTool = async function projectTool(context) {
|
||||||
|
if (context.toolId !== 'rebuild-cache') throw new Error(`Unknown Drupal tool '${context.toolId}'`)
|
||||||
|
await context.run('Rebuild Drupal cache', 'docker', phpExec(context, 'vendor/bin/drush', 'cache:rebuild'))
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"id": "drupal",
|
||||||
|
"name": "Drupal",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "application",
|
||||||
|
"description": "Drupal 11 CMS with Composer, Drush, and the recommended web-root layout.",
|
||||||
|
"main": "main/index.cjs",
|
||||||
|
"aurora": { "core": "2.0.0-alpha.24", "moduleApi": "1.0.0" },
|
||||||
|
"dependencies": [],
|
||||||
|
"conflicts": [],
|
||||||
|
"defaults": { "docroot": "web" },
|
||||||
|
"settings": [],
|
||||||
|
"creation": {
|
||||||
|
"databases": ["mariadb", "mysql", "postgres"],
|
||||||
|
"phpVersions": ["8.4", "8.3"],
|
||||||
|
"intro": {
|
||||||
|
"title": "Drupal 11 install",
|
||||||
|
"description": "Composer downloads Drupal and Drush, then Aurora configures the site automatically.",
|
||||||
|
"icon": "globe"
|
||||||
|
},
|
||||||
|
"setup": [
|
||||||
|
{ "id": "site_name", "label": "Site name", "type": "text", "default": "", "placeholder": "My Drupal Site", "required": true, "icon": "title", "span": 2 },
|
||||||
|
{ "id": "admin_user", "label": "Admin username", "type": "text", "default": "admin", "required": true, "icon": "user" },
|
||||||
|
{ "id": "admin_password", "label": "Admin password", "type": "text", "default": "", "required": true, "secret": true, "icon": "key" },
|
||||||
|
{ "id": "admin_email", "label": "Admin email", "type": "text", "default": "", "required": true, "placeholder": "[email protected]", "icon": "mail", "span": 2 },
|
||||||
|
{ "id": "profile", "label": "Installation profile", "type": "select", "default": "standard", "advanced": true, "icon": "settings", "options": ["standard", "minimal", "demo_umami"], "optionLabels": { "standard": "Standard", "minimal": "Minimal", "demo_umami": "Umami demo" } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"adminPath": "/admin/",
|
||||||
|
"summary": { "metadataKey": "drupalVersion", "labels": {}, "fallback": "Drupal 11" },
|
||||||
|
"tools": [{ "id": "rebuild-cache", "label": "Rebuild Drupal cache" }]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "@aurora/module-drupal",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Drupal application module for Aurora Dockside",
|
||||||
|
"files": ["manifest.json", "main", "README.md"]
|
||||||
|
}
|
||||||
@@ -132,9 +132,9 @@ async function renderCompose(c: AuroraConfig): Promise<string> {
|
|||||||
async function writePhpDockerfile(root: string, xdebug = false): Promise<void> {
|
async function writePhpDockerfile(root: string, xdebug = false): Promise<void> {
|
||||||
await writeFile(phpDockerfilePath(root), `ARG PHP_VERSION=8.4
|
await writeFile(phpDockerfilePath(root), `ARG PHP_VERSION=8.4
|
||||||
FROM php:${'${PHP_VERSION}'}-fpm-alpine
|
FROM php:${'${PHP_VERSION}'}-fpm-alpine
|
||||||
RUN apk add --no-cache icu-dev libzip-dev libpng-dev libjpeg-turbo-dev freetype-dev oniguruma-dev postgresql-dev \
|
RUN apk add --no-cache icu-dev libzip-dev libpng-dev libjpeg-turbo-dev freetype-dev oniguruma-dev postgresql-dev curl-dev libxml2-dev \
|
||||||
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
|
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||||||
&& docker-php-ext-install -j2 mysqli pdo_mysql pdo_pgsql intl zip gd mbstring
|
&& docker-php-ext-install -j2 mysqli pdo_mysql pdo_pgsql intl zip gd mbstring curl dom opcache
|
||||||
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||||
${xdebug ? 'RUN apk add --no-cache $PHPIZE_DEPS linux-headers && pecl install xdebug && docker-php-ext-enable xdebug' : ''}
|
${xdebug ? 'RUN apk add --no-cache $PHPIZE_DEPS linux-headers && pecl install xdebug && docker-php-ext-enable xdebug' : ''}
|
||||||
`)
|
`)
|
||||||
@@ -211,7 +211,9 @@ export async function createProject(root: string, name: string, type: string, do
|
|||||||
if (stack?.adminer !== false) modules.push('adminer')
|
if (stack?.adminer !== false) modules.push('adminer')
|
||||||
if (stack?.redis) modules.push('redis')
|
if (stack?.redis) modules.push('redis')
|
||||||
if (stack?.mailpit) modules.push('mailpit')
|
if (stack?.mailpit) modules.push('mailpit')
|
||||||
const config: AuroraConfig = { name, type: normalizedType, docroot: defaultDocroot, php: stack?.phpVersion || '8.4', node: stack?.nodeVersion || '24', webserver: stack?.webServer || 'nginx', database: stack?.database || 'mariadb', databaseVersion: stack?.databaseVersion || '11.8', modules, moduleSettings: {}, primaryProtocol: 'https', xdebug: stack?.xdebug === true }
|
const phpVersion = stack?.phpVersion || application.creation?.phpVersions?.[0] || '8.4'
|
||||||
|
if (application.creation?.phpVersions?.length && !application.creation.phpVersions.includes(phpVersion)) throw new Error(`${application.name} does not support PHP ${phpVersion}`)
|
||||||
|
const config: AuroraConfig = { name, type: normalizedType, docroot: defaultDocroot, php: phpVersion, node: stack?.nodeVersion || '24', webserver: stack?.webServer || 'nginx', database: stack?.database || 'mariadb', databaseVersion: stack?.databaseVersion || '11.8', modules, moduleSettings: {}, primaryProtocol: 'https', xdebug: stack?.xdebug === true }
|
||||||
await writeConfig(root, config); await writeWebServerConfig(root, config); await writePhpDockerfile(root, config.xdebug)
|
await writeConfig(root, config); await writeWebServerConfig(root, config); await writePhpDockerfile(root, config.xdebug)
|
||||||
const reg = await loadRegistry(); reg.projects[name] = root; await saveRegistry(reg)
|
const reg = await loadRegistry(); reg.projects[name] = root; await saveRegistry(reg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ describe('external module registry', () => {
|
|||||||
expect(() => validateModuleManifest({ ...manifest, main: '../outside.cjs' })).toThrow(/may not leave/)
|
expect(() => validateModuleManifest({ ...manifest, main: '../outside.cjs' })).toThrow(/may not leave/)
|
||||||
expect(() => validateModuleManifest({ ...manifest, dependencies: ['../escape'] })).toThrow(/module id array/)
|
expect(() => validateModuleManifest({ ...manifest, dependencies: ['../escape'] })).toThrow(/module id array/)
|
||||||
expect(() => validateModuleManifest({ ...manifest, project: { adminPath: 'admin' } })).toThrow(/must start with/)
|
expect(() => validateModuleManifest({ ...manifest, project: { adminPath: 'admin' } })).toThrow(/must start with/)
|
||||||
|
expect(() => validateModuleManifest({ ...manifest, creation: { phpVersions: ['latest'] } })).toThrow(/PHP minor versions/)
|
||||||
})
|
})
|
||||||
it('rejects symbolic links in package paths', async () => {
|
it('rejects symbolic links in package paths', async () => {
|
||||||
const userData = await temp('aurora-user-'); const source = await packageDir(); await mkdir(join(source, 'main')); await symlink('/tmp', join(source, 'main', 'escape'))
|
const userData = await temp('aurora-user-'); const source = await packageDir(); await mkdir(join(source, 'main')); await symlink('/tmp', join(source, 'main', 'escape'))
|
||||||
@@ -135,4 +136,14 @@ describe('external module registry', () => {
|
|||||||
expect((await installModulePackage(archive, userData)).manifest.id).toBe('wordpress')
|
expect((await installModulePackage(archive, userData)).manifest.id).toBe('wordpress')
|
||||||
expect((await getModuleRegistry(userData)).map((item) => item.id)).toEqual(['wordpress'])
|
expect((await getModuleRegistry(userData)).map((item) => item.id)).toEqual(['wordpress'])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('accepts and packages the Drupal application module contract', async () => {
|
||||||
|
const packageRoot = resolve(process.cwd(), 'packages/aurora-module-drupal')
|
||||||
|
const actual = JSON.parse(await readFile(join(packageRoot, 'manifest.json'), 'utf8'))
|
||||||
|
expect(validateModuleManifest(actual)).toMatchObject({ id: 'drupal', defaults: { docroot: 'web' } })
|
||||||
|
const archive = join(await temp('aurora-pac-'), 'drupal.pac')
|
||||||
|
await execFileAsync('zip', ['-qr', archive, '.'], { cwd: packageRoot })
|
||||||
|
const userData = await temp('aurora-user-')
|
||||||
|
expect((await installModulePackage(archive, userData)).manifest.id).toBe('drupal')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -51,7 +51,11 @@ export function validateModuleManifest(value: unknown): AuroraModuleManifest {
|
|||||||
if (!compatible(aurora.core, CORE_VERSION)) throw new Error(`Module requires Aurora Core '${aurora.core}', running '${CORE_VERSION}'`)
|
if (!compatible(aurora.core, CORE_VERSION)) throw new Error(`Module requires Aurora Core '${aurora.core}', running '${CORE_VERSION}'`)
|
||||||
if (!compatible(aurora.moduleApi, MODULE_API_VERSION)) throw new Error(`Module API '${aurora.moduleApi}' is incompatible with '${MODULE_API_VERSION}'`)
|
if (!compatible(aurora.moduleApi, MODULE_API_VERSION)) throw new Error(`Module API '${aurora.moduleApi}' is incompatible with '${MODULE_API_VERSION}'`)
|
||||||
if (m.main !== undefined) validateRelativePackagePath(m.main, 'main')
|
if (m.main !== undefined) validateRelativePackagePath(m.main, 'main')
|
||||||
if (m.creation && typeof m.creation === 'object') validateSettings((m.creation as Record<string, unknown>).setup ?? [], 'creation.setup')
|
if (m.creation && typeof m.creation === 'object') {
|
||||||
|
const creation = m.creation as Record<string, unknown>
|
||||||
|
validateSettings(creation.setup ?? [], 'creation.setup')
|
||||||
|
if (creation.phpVersions !== undefined && (!Array.isArray(creation.phpVersions) || !creation.phpVersions.every((version) => typeof version === 'string' && /^\d+\.\d+$/.test(version)))) throw new Error('creation.phpVersions must contain PHP minor versions')
|
||||||
|
}
|
||||||
if (m.project !== undefined) {
|
if (m.project !== undefined) {
|
||||||
if (!m.project || typeof m.project !== 'object' || Array.isArray(m.project)) throw new Error('project must be an object')
|
if (!m.project || typeof m.project !== 'object' || Array.isArray(m.project)) throw new Error('project must be an object')
|
||||||
const project = m.project as Record<string, unknown>
|
const project = m.project as Record<string, unknown>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export async function runModuleLifecycleHook(moduleId: string, hook: AuroraModul
|
|||||||
if (typeof handler !== 'function') return false
|
if (typeof handler !== 'function') return false
|
||||||
const directory = options.directory
|
const directory = options.directory
|
||||||
const urls = options.projectName ? projectUrls(options.projectName) : undefined
|
const urls = options.projectName ? projectUrls(options.projectName) : undefined
|
||||||
|
const projectConfig = directory ? await getProjectConfig(directory) : undefined
|
||||||
const run = async (label: string, command: string, args: string[]): Promise<void> => {
|
const run = async (label: string, command: string, args: string[]): Promise<void> => {
|
||||||
if (options.sender && options.operationId) {
|
if (options.sender && options.operationId) {
|
||||||
if (!options.sender.isDestroyed()) options.sender.send('terminal:data', { operationId: options.operationId, stream: 'stdout', chunk: `\n[${manifest.name}] ${label}\n` })
|
if (!options.sender.isDestroyed()) options.sender.send('terminal:data', { operationId: options.operationId, stream: 'stdout', chunk: `\n[${manifest.name}] ${label}\n` })
|
||||||
@@ -49,6 +50,7 @@ export async function runModuleLifecycleHook(moduleId: string, hook: AuroraModul
|
|||||||
projectName: options.projectName,
|
projectName: options.projectName,
|
||||||
toolId: options.toolId,
|
toolId: options.toolId,
|
||||||
settings: Object.freeze({ ...(options.settings ?? {}) }),
|
settings: Object.freeze({ ...(options.settings ?? {}) }),
|
||||||
|
environment: projectConfig ? Object.freeze({ php: projectConfig.php, node: projectConfig.node, webserver: projectConfig.webserver, database: projectConfig.database, databaseVersion: projectConfig.databaseVersion, docroot: projectConfig.docroot }) : undefined,
|
||||||
urls: urls ? Object.freeze(urls) : undefined,
|
urls: urls ? Object.freeze(urls) : undefined,
|
||||||
run,
|
run,
|
||||||
ensureRouter,
|
ensureRouter,
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ export function CreateProjectModal({ onClose }: { onClose: () => void }): React.
|
|||||||
<div>
|
<div>
|
||||||
<label className={labelClass}>Project type</label>
|
<label className={labelClass}>Project type</label>
|
||||||
<div className="grid gap-2 sm:grid-cols-2">
|
<div className="grid gap-2 sm:grid-cols-2">
|
||||||
{applicationModules.map((module) => ({ value: module.id, label: module.name, defaults: module.defaults })).map((t) => {
|
{applicationModules.map((module) => ({ value: module.id, label: module.name, defaults: module.defaults, creation: module.creation })).map((t) => {
|
||||||
const Icon = TYPE_ICONS[t.value] ?? Boxes
|
const Icon = TYPE_ICONS[t.value] ?? Boxes
|
||||||
const isSelected = projectType === t.value
|
const isSelected = projectType === t.value
|
||||||
|
|
||||||
@@ -234,6 +234,7 @@ export function CreateProjectModal({ onClose }: { onClose: () => void }): React.
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setProjectType(t.value)
|
setProjectType(t.value)
|
||||||
|
setPhpVersion(t.creation?.phpVersions?.[0] ?? '8.4')
|
||||||
if (!docroot.trim() && t.defaults?.docroot) setDocroot(t.defaults.docroot)
|
if (!docroot.trim() && t.defaults?.docroot) setDocroot(t.defaults.docroot)
|
||||||
}}
|
}}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
@@ -272,7 +273,7 @@ export function CreateProjectModal({ onClose }: { onClose: () => void }): React.
|
|||||||
<p className="mt-0.5 text-xs text-neutral-500 dark:text-neutral-400">Aurora core owns the runtime; the application is a module layered on top.</p>
|
<p className="mt-0.5 text-xs text-neutral-500 dark:text-neutral-400">Aurora core owns the runtime; the application is a module layered on top.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
<div><label className={labelClass}>PHP</label><select className={fieldClass} value={phpVersion} onChange={(e)=>setPhpVersion(e.target.value)}>{['8.2','8.3','8.4','8.5'].map(v=><option key={v}>{v}</option>)}</select></div>
|
<div><label className={labelClass}>PHP</label><select className={fieldClass} value={phpVersion} onChange={(e)=>setPhpVersion(e.target.value)}>{(selectedModule?.creation?.phpVersions ?? ['8.2','8.3','8.4','8.5']).map(v=><option key={v}>{v}</option>)}</select></div>
|
||||||
<div><label className={labelClass}>Node.js</label><select className={fieldClass} value={nodeVersion} onChange={(e)=>setNodeVersion(e.target.value)}>{['20','22','24'].map(v=><option key={v}>{v}</option>)}</select></div>
|
<div><label className={labelClass}>Node.js</label><select className={fieldClass} value={nodeVersion} onChange={(e)=>setNodeVersion(e.target.value)}>{['20','22','24'].map(v=><option key={v}>{v}</option>)}</select></div>
|
||||||
<div><label className={labelClass}>Web server</label><select className={fieldClass} value={webServer} onChange={(e)=>setWebServer(e.target.value as 'nginx'|'apache')}><option value="nginx">nginx</option><option value="apache">Apache</option></select></div>
|
<div><label className={labelClass}>Web server</label><select className={fieldClass} value={webServer} onChange={(e)=>setWebServer(e.target.value as 'nginx'|'apache')}><option value="nginx">nginx</option><option value="apache">Apache</option></select></div>
|
||||||
<div><label className={labelClass}>Database</label><select className={fieldClass} value={`${database}:${databaseVersion}`} onChange={(e)=>{const [kind,version]=e.target.value.split(':');setDatabase(kind as 'mariadb'|'mysql'|'postgres');setDatabaseVersion(version)}}>{selectedModule?.creation?.databases?.includes('mariadb') !== false && <><option value="mariadb:11.8">MariaDB 11.8</option><option value="mariadb:10.11">MariaDB 10.11</option></>}{selectedModule?.creation?.databases?.includes('mysql') !== false && <><option value="mysql:8.4">MySQL 8.4</option><option value="mysql:8.0">MySQL 8.0</option></>}{selectedModule?.creation?.databases?.includes('postgres') !== false && <><option value="postgres:17">PostgreSQL 17</option><option value="postgres:16">PostgreSQL 16</option></>}</select></div>
|
<div><label className={labelClass}>Database</label><select className={fieldClass} value={`${database}:${databaseVersion}`} onChange={(e)=>{const [kind,version]=e.target.value.split(':');setDatabase(kind as 'mariadb'|'mysql'|'postgres');setDatabaseVersion(version)}}>{selectedModule?.creation?.databases?.includes('mariadb') !== false && <><option value="mariadb:11.8">MariaDB 11.8</option><option value="mariadb:10.11">MariaDB 10.11</option></>}{selectedModule?.creation?.databases?.includes('mysql') !== false && <><option value="mysql:8.4">MySQL 8.4</option><option value="mysql:8.0">MySQL 8.0</option></>}{selectedModule?.creation?.databases?.includes('postgres') !== false && <><option value="postgres:17">PostgreSQL 17</option><option value="postgres:16">PostgreSQL 16</option></>}</select></div>
|
||||||
|
|||||||
@@ -162,10 +162,10 @@ export function ProjectDetail({ name }: { name: string }): React.JSX.Element {
|
|||||||
const services = Object.values(project.services)
|
const services = Object.values(project.services)
|
||||||
const runningServices = services.filter((service) => service.status === 'running').length
|
const runningServices = services.filter((service) => service.status === 'running').length
|
||||||
|
|
||||||
const phpVersions =
|
const supportedPhpVersions = applicationManifest?.creation?.phpVersions ?? PHP_VERSIONS
|
||||||
project.php_version && !PHP_VERSIONS.includes(project.php_version)
|
const phpVersions = project.php_version && !supportedPhpVersions.includes(project.php_version)
|
||||||
? [project.php_version, ...PHP_VERSIONS]
|
? [project.php_version, ...supportedPhpVersions]
|
||||||
: PHP_VERSIONS
|
: supportedPhpVersions
|
||||||
|
|
||||||
const currentDatabase = `${project.dbinfo.database_type}:${project.dbinfo.database_version}`
|
const currentDatabase = `${project.dbinfo.database_type}:${project.dbinfo.database_version}`
|
||||||
const projectContribution = applicationManifest?.project
|
const projectContribution = applicationManifest?.project
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ export interface AuroraModuleManifest {
|
|||||||
aurora: { core: string; moduleApi: string }
|
aurora: { core: string; moduleApi: string }
|
||||||
creation?: {
|
creation?: {
|
||||||
databases?: Array<'mariadb' | 'mysql' | 'postgres'>
|
databases?: Array<'mariadb' | 'mysql' | 'postgres'>
|
||||||
|
phpVersions?: string[]
|
||||||
setup?: AuroraModuleSetting[]
|
setup?: AuroraModuleSetting[]
|
||||||
intro?: { title: string; description: string; icon?: string }
|
intro?: { title: string; description: string; icon?: string }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ The resulting `.pac` file is written to `dist/module-catalog/`.
|
|||||||
|
|
||||||
Lifecycle exports are optional. `projectCreate` runs during initial provisioning, `projectStart` after the containers start, `projectRemove` before project-scoped removal, and `packageUninstall` before the installed package is deleted. A manifest entry in `project.tools` calls `projectTool(context)` with its `id` in `context.toolId`.
|
Lifecycle exports are optional. `projectCreate` runs during initial provisioning, `projectStart` after the containers start, `projectRemove` before project-scoped removal, and `packageUninstall` before the installed package is deleted. A manifest entry in `project.tools` calls `projectTool(context)` with its `id` in `context.toolId`.
|
||||||
|
|
||||||
The context provides `moduleId`, `hook`, `directory`, `projectName`, `settings`, `urls`, and `toolId`. It also provides `run(label, command, args)`, `ensureRouter()`, `setProjectMetadata(values)`, and `saveCredentials(values)`. Project-only values and helpers are unavailable to `packageUninstall`.
|
The context provides `moduleId`, `hook`, `directory`, `projectName`, `settings`, `urls`, `environment`, and `toolId`. `environment` contains the project's PHP, Node.js, web-server, database, database-version, and document-root choices. The context also provides `run(label, command, args)`, `ensureRouter()`, `setProjectMetadata(values)`, and `saveCredentials(values)`. Project-only values and helpers are unavailable to `packageUninstall`.
|
||||||
|
|||||||
Reference in New Issue
Block a user