Initial commit
This commit is contained in:
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Frontend development
|
||||||
|
node_modules/
|
||||||
|
*.local
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
.cache/
|
||||||
|
.vite/
|
||||||
|
.temp/
|
||||||
|
.tmp/
|
||||||
|
|
||||||
|
# Frontend build
|
||||||
|
/static/
|
||||||
|
|
||||||
|
# Backend development
|
||||||
|
/lib/vendor/
|
||||||
|
coverage/
|
||||||
|
phpunit.xml.cache
|
||||||
|
.phpunit.result.cache
|
||||||
|
.php-cs-fixer.cache
|
||||||
|
.phpstan.cache
|
||||||
|
.phpactor/
|
||||||
|
|
||||||
|
# Editors
|
||||||
|
.DS_Store
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
9
composer.json
Normal file
9
composer.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "ktxm/module_manager",
|
||||||
|
"type": "project",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"KTXM\\ModuleManager\\": "lib/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
80
lib/Module.php
Normal file
80
lib/Module.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace KTXM\ModuleManager;
|
||||||
|
|
||||||
|
use KTXF\Module\ModuleBrowserInterface;
|
||||||
|
use KTXF\Module\ModuleInstanceAbstract;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module Manager Module
|
||||||
|
*/
|
||||||
|
class Module extends ModuleInstanceAbstract implements ModuleBrowserInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public function handle(): string
|
||||||
|
{
|
||||||
|
return 'module_manager';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return 'Module Manager';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function author(): string
|
||||||
|
{
|
||||||
|
return 'Ktrix';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description(): string
|
||||||
|
{
|
||||||
|
return 'Module management interface - provides module listing, installation, uninstallation, enabling, disabling, and upgrading capabilities';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function version(): string
|
||||||
|
{
|
||||||
|
return '0.0.1';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function permissions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'module_manager' => [
|
||||||
|
'label' => 'Access Module Manager',
|
||||||
|
'description' => 'View and access the module manager interface',
|
||||||
|
'group' => 'Module Management'
|
||||||
|
],
|
||||||
|
'module_manager.modules.view' => [
|
||||||
|
'label' => 'View Modules',
|
||||||
|
'description' => 'View list of installed and available modules',
|
||||||
|
'group' => 'Module Management'
|
||||||
|
],
|
||||||
|
'module_manager.modules.manage' => [
|
||||||
|
'label' => 'Manage Modules',
|
||||||
|
'description' => 'Install, uninstall, enable, and disable modules',
|
||||||
|
'group' => 'Module Management'
|
||||||
|
],
|
||||||
|
'module_manager.modules.*' => [
|
||||||
|
'label' => 'Full Module Management',
|
||||||
|
'description' => 'All module management operations',
|
||||||
|
'group' => 'Module Management'
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerBI(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'handle' => $this->handle(),
|
||||||
|
'namespace' => 'ModuleManager',
|
||||||
|
'version' => $this->version(),
|
||||||
|
'label' => $this->label(),
|
||||||
|
'author' => $this->author(),
|
||||||
|
'description' => $this->description(),
|
||||||
|
'boot' => 'static/module.mjs',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
2136
package-lock.json
generated
Normal file
2136
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
package.json
Normal file
33
package.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "module_manager",
|
||||||
|
"description": "Ktrix Module Manager Module",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"license": "AGPL-3.0-or-later",
|
||||||
|
"author": "Ktrix",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build --mode production --config vite.config.ts",
|
||||||
|
"dev": "vite build --mode development --config vite.config.ts",
|
||||||
|
"watch": "vite build --mode development --watch --config vite.config.ts",
|
||||||
|
"typecheck": "vue-tsc --noEmit",
|
||||||
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@mdi/js": "^7.4.47",
|
||||||
|
"pinia": "^2.3.0",
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-router": "^4.5.0",
|
||||||
|
"vuetify": "^3.7.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.10.2",
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"@vue/tsconfig": "^0.7.0",
|
||||||
|
"sass": "^1.77.1",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"vite": "^6.0.6",
|
||||||
|
"vite-plugin-vuetify": "^2.1.0",
|
||||||
|
"vue-tsc": "^2.2.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/integrations.ts
Normal file
15
src/integrations.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { ModuleIntegrations } from "@KTXC/types/moduleTypes";
|
||||||
|
|
||||||
|
const integrations: ModuleIntegrations = {
|
||||||
|
admin_settings_menu: [
|
||||||
|
{
|
||||||
|
id: 'module_manager',
|
||||||
|
label: 'Modules',
|
||||||
|
path: '/modules',
|
||||||
|
icon: 'mdi-package-variant',
|
||||||
|
priority: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default integrations;
|
||||||
14
src/main.ts
Normal file
14
src/main.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import routes from '@/routes'
|
||||||
|
import integrations from '@/integrations'
|
||||||
|
import type { App as Vue } from 'vue'
|
||||||
|
|
||||||
|
// CSS filename is injected by the vite plugin at build time
|
||||||
|
export const css = ['__CSS_FILENAME_PLACEHOLDER__']
|
||||||
|
|
||||||
|
export { routes, integrations }
|
||||||
|
|
||||||
|
export default {
|
||||||
|
install(app: Vue) {
|
||||||
|
// Module-specific plugins can be registered here
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/routes.ts
Normal file
9
src/routes.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
name: 'module_manager',
|
||||||
|
path: '/modules',
|
||||||
|
component: () => import('@/views/ModuleList.vue'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
40
src/services/moduleService.ts
Normal file
40
src/services/moduleService.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import type { Module, ModuleAction } from '@/types/module'
|
||||||
|
|
||||||
|
const API_BASE = '/modules'
|
||||||
|
|
||||||
|
export async function fetchModules(): Promise<Module[]> {
|
||||||
|
const response = await fetch(`${API_BASE}/list`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch modules: ${response.statusText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
return data.modules || []
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function manageModule(handle: string, action: ModuleAction): Promise<{ message?: string; error?: string }> {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('handle', handle)
|
||||||
|
formData.append('action', action)
|
||||||
|
|
||||||
|
const response = await fetch(`${API_BASE}/manage`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.error || `Failed to ${action} module`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
1
src/style.css
Normal file
1
src/style.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/* module_manager module styles */
|
||||||
11
src/types/module.ts
Normal file
11
src/types/module.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export interface Module {
|
||||||
|
handle: string
|
||||||
|
label: string
|
||||||
|
author: string
|
||||||
|
description: string
|
||||||
|
version: string
|
||||||
|
installed: boolean
|
||||||
|
enabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ModuleAction = 'install' | 'uninstall' | 'enable' | 'disable' | 'upgrade'
|
||||||
302
src/views/ModuleList.vue
Normal file
302
src/views/ModuleList.vue
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { fetchModules, manageModule } from '@/services/moduleService'
|
||||||
|
import type { Module, ModuleAction } from '@/types/module'
|
||||||
|
import {
|
||||||
|
mdiPackageVariant,
|
||||||
|
mdiDownload,
|
||||||
|
mdiTrashCan,
|
||||||
|
mdiCheckCircle,
|
||||||
|
mdiCloseCircle,
|
||||||
|
mdiUpdate,
|
||||||
|
mdiLoading,
|
||||||
|
} from '@mdi/js'
|
||||||
|
|
||||||
|
const modules = ref<Module[]>([])
|
||||||
|
const loading = ref(true)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
const actionLoading = ref<string | null>(null)
|
||||||
|
const snackbar = ref(false)
|
||||||
|
const snackbarText = ref('')
|
||||||
|
const snackbarColor = ref('success')
|
||||||
|
const search = ref('')
|
||||||
|
const filterStatus = ref<'all' | 'installed' | 'not-installed' | 'enabled' | 'disabled'>('all')
|
||||||
|
|
||||||
|
const filteredModules = computed(() => {
|
||||||
|
let result = modules.value
|
||||||
|
|
||||||
|
// Filter by status
|
||||||
|
if (filterStatus.value === 'installed') {
|
||||||
|
result = result.filter(m => m.installed)
|
||||||
|
} else if (filterStatus.value === 'not-installed') {
|
||||||
|
result = result.filter(m => !m.installed)
|
||||||
|
} else if (filterStatus.value === 'enabled') {
|
||||||
|
result = result.filter(m => m.enabled)
|
||||||
|
} else if (filterStatus.value === 'disabled') {
|
||||||
|
result = result.filter(m => m.installed && !m.enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by search
|
||||||
|
if (search.value) {
|
||||||
|
const searchLower = search.value.toLowerCase()
|
||||||
|
result = result.filter(
|
||||||
|
m =>
|
||||||
|
m.handle.toLowerCase().includes(searchLower) ||
|
||||||
|
m.label.toLowerCase().includes(searchLower) ||
|
||||||
|
m.description.toLowerCase().includes(searchLower) ||
|
||||||
|
m.author.toLowerCase().includes(searchLower)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadModules() {
|
||||||
|
loading.value = true
|
||||||
|
error.value = null
|
||||||
|
try {
|
||||||
|
modules.value = await fetchModules()
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e instanceof Error ? e.message : 'Failed to load modules'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleAction(module: Module, action: ModuleAction) {
|
||||||
|
const key = `${module.handle}-${action}`
|
||||||
|
actionLoading.value = key
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await manageModule(module.handle, action)
|
||||||
|
|
||||||
|
snackbarText.value = result.message || `Module ${action}ed successfully`
|
||||||
|
snackbarColor.value = 'success'
|
||||||
|
snackbar.value = true
|
||||||
|
|
||||||
|
// Reload modules to get updated state
|
||||||
|
await loadModules()
|
||||||
|
} catch (e) {
|
||||||
|
snackbarText.value = e instanceof Error ? e.message : `Failed to ${action} module`
|
||||||
|
snackbarColor.value = 'error'
|
||||||
|
snackbar.value = true
|
||||||
|
} finally {
|
||||||
|
actionLoading.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusColor(module: Module): string {
|
||||||
|
if (!module.installed) return 'grey'
|
||||||
|
if (module.enabled) return 'success'
|
||||||
|
return 'warning'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusText(module: Module): string {
|
||||||
|
if (!module.installed) return 'Not Installed'
|
||||||
|
if (module.enabled) return 'Enabled'
|
||||||
|
return 'Disabled'
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActionLoading(module: Module, action: string): boolean {
|
||||||
|
return actionLoading.value === `${module.handle}-${action}`
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadModules()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-container fluid>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-icon :icon="mdiPackageVariant" class="mr-2"></v-icon>
|
||||||
|
<span>Module Manager</span>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
variant="text"
|
||||||
|
:loading="loading"
|
||||||
|
@click="loadModules"
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</v-btn>
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-card-text>
|
||||||
|
<v-row class="mb-4">
|
||||||
|
<v-col cols="12" md="8">
|
||||||
|
<v-text-field
|
||||||
|
v-model="search"
|
||||||
|
label="Search modules"
|
||||||
|
prepend-inner-icon="mdi-magnify"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
clearable
|
||||||
|
hide-details
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-select
|
||||||
|
v-model="filterStatus"
|
||||||
|
:items="[
|
||||||
|
{ title: 'All Modules', value: 'all' },
|
||||||
|
{ title: 'Installed', value: 'installed' },
|
||||||
|
{ title: 'Not Installed', value: 'not-installed' },
|
||||||
|
{ title: 'Enabled', value: 'enabled' },
|
||||||
|
{ title: 'Disabled', value: 'disabled' },
|
||||||
|
]"
|
||||||
|
label="Filter by status"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
hide-details
|
||||||
|
></v-select>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-alert v-if="error" type="error" class="mb-4">
|
||||||
|
{{ error }}
|
||||||
|
</v-alert>
|
||||||
|
|
||||||
|
<v-progress-linear v-if="loading" indeterminate color="primary"></v-progress-linear>
|
||||||
|
|
||||||
|
<v-row v-else>
|
||||||
|
<v-col
|
||||||
|
v-for="module in filteredModules"
|
||||||
|
:key="module.handle"
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
lg="4"
|
||||||
|
>
|
||||||
|
<v-card elevation="2" class="module-card">
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-icon :icon="mdiPackageVariant" class="mr-2"></v-icon>
|
||||||
|
<span>{{ module.label }}</span>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-chip
|
||||||
|
:color="getStatusColor(module)"
|
||||||
|
size="small"
|
||||||
|
variant="flat"
|
||||||
|
>
|
||||||
|
{{ getStatusText(module) }}
|
||||||
|
</v-chip>
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-card-subtitle>
|
||||||
|
<div class="text-caption">{{ module.handle }}</div>
|
||||||
|
<div class="text-caption text-grey">by {{ module.author }} • v{{ module.version }}</div>
|
||||||
|
</v-card-subtitle>
|
||||||
|
|
||||||
|
<v-card-text>
|
||||||
|
<p class="module-description">{{ module.description }}</p>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn
|
||||||
|
v-if="!module.installed"
|
||||||
|
color="primary"
|
||||||
|
variant="tonal"
|
||||||
|
size="small"
|
||||||
|
:loading="isActionLoading(module, 'install')"
|
||||||
|
@click="handleAction(module, 'install')"
|
||||||
|
>
|
||||||
|
<v-icon :icon="mdiDownload" start></v-icon>
|
||||||
|
Install
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<v-btn
|
||||||
|
v-if="module.enabled"
|
||||||
|
color="warning"
|
||||||
|
variant="tonal"
|
||||||
|
size="small"
|
||||||
|
:loading="isActionLoading(module, 'disable')"
|
||||||
|
@click="handleAction(module, 'disable')"
|
||||||
|
>
|
||||||
|
<v-icon :icon="mdiCloseCircle" start></v-icon>
|
||||||
|
Disable
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
v-else
|
||||||
|
color="success"
|
||||||
|
variant="tonal"
|
||||||
|
size="small"
|
||||||
|
:loading="isActionLoading(module, 'enable')"
|
||||||
|
@click="handleAction(module, 'enable')"
|
||||||
|
>
|
||||||
|
<v-icon :icon="mdiCheckCircle" start></v-icon>
|
||||||
|
Enable
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
color="info"
|
||||||
|
variant="tonal"
|
||||||
|
size="small"
|
||||||
|
:loading="isActionLoading(module, 'upgrade')"
|
||||||
|
@click="handleAction(module, 'upgrade')"
|
||||||
|
>
|
||||||
|
<v-icon :icon="mdiUpdate" start></v-icon>
|
||||||
|
Upgrade
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
color="error"
|
||||||
|
variant="tonal"
|
||||||
|
size="small"
|
||||||
|
:loading="isActionLoading(module, 'uninstall')"
|
||||||
|
@click="handleAction(module, 'uninstall')"
|
||||||
|
>
|
||||||
|
<v-icon :icon="mdiTrashCan" start></v-icon>
|
||||||
|
Uninstall
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col v-if="filteredModules.length === 0" cols="12">
|
||||||
|
<v-alert type="info">
|
||||||
|
No modules found matching your criteria.
|
||||||
|
</v-alert>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-snackbar
|
||||||
|
v-model="snackbar"
|
||||||
|
:color="snackbarColor"
|
||||||
|
:timeout="3000"
|
||||||
|
>
|
||||||
|
{{ snackbarText }}
|
||||||
|
</v-snackbar>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.module-card {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.v-card-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-description {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
min-height: 4.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
14
tsconfig.app.json
Normal file
14
tsconfig.app.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": ["src/**/*", "src/**/*.vue"],
|
||||||
|
"exclude": ["node_modules"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"],
|
||||||
|
"@KTXC/*": ["../../core/src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": ["src/**/*", "src/**/*.vue"],
|
||||||
|
"exclude": ["node_modules"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"],
|
||||||
|
"@KTXC/*": ["../../core/src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
tsconfig.node.json
Normal file
18
tsconfig.node.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.node.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"nightwatch.conf.*",
|
||||||
|
"playwright.config.*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
||||||
57
vite.config.ts
Normal file
57
vite.config.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
{
|
||||||
|
name: 'inject-css-filename',
|
||||||
|
enforce: 'post',
|
||||||
|
generateBundle(_options, bundle) {
|
||||||
|
const cssFile = Object.keys(bundle).find(name => name.endsWith('.css'))
|
||||||
|
if (!cssFile) return
|
||||||
|
|
||||||
|
for (const fileName of Object.keys(bundle)) {
|
||||||
|
const chunk = bundle[fileName]
|
||||||
|
if (chunk.type === 'chunk' && chunk.code.includes('__CSS_FILENAME_PLACEHOLDER__')) {
|
||||||
|
chunk.code = chunk.code.replace(/__CSS_FILENAME_PLACEHOLDER__/g, `static/${cssFile}`)
|
||||||
|
console.log(`Injected CSS filename "static/${cssFile}" into ${fileName}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
'@KTXC': path.resolve(__dirname, '../../core/src')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
'process.env': {},
|
||||||
|
'process': undefined,
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: 'static',
|
||||||
|
emptyOutDir: true,
|
||||||
|
sourcemap: true,
|
||||||
|
lib: {
|
||||||
|
entry: path.resolve(__dirname, 'src/main.ts'),
|
||||||
|
formats: ['es'],
|
||||||
|
fileName: () => 'module.mjs',
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: ['vue', 'vue-router', 'pinia'],
|
||||||
|
output: {
|
||||||
|
assetFileNames: (assetInfo) => {
|
||||||
|
if (assetInfo.name?.endsWith('.css')) {
|
||||||
|
return 'module_manager-[hash].css'
|
||||||
|
}
|
||||||
|
return '[name]-[hash][extname]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user