@webmate-studio/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/wm.js +102 -0
- package/package.json +37 -0
- package/src/commands/build.js +113 -0
- package/src/commands/dev.js +29 -0
- package/src/commands/generate.js +579 -0
- package/src/commands/info.js +49 -0
- package/src/commands/init.js +452 -0
- package/src/commands/login.js +193 -0
- package/src/commands/logout.js +20 -0
- package/src/commands/prop.js +286 -0
- package/src/commands/push.js +275 -0
- package/src/commands/switch.js +131 -0
- package/src/index.js +4 -0
- package/src/templates/islands/alpine.js +44 -0
- package/src/templates/islands/lit.js +90 -0
- package/src/templates/islands/preact.jsx +52 -0
- package/src/templates/islands/react.jsx +50 -0
- package/src/templates/islands/svelte-component.svelte +36 -0
- package/src/templates/islands/svelte.js +31 -0
- package/src/templates/islands/vanilla.js +71 -0
- package/src/templates/islands/vue.js +65 -0
- package/src/utils/auth.js +125 -0
- package/src/utils/bundler.js +163 -0
- package/src/utils/config.js +103 -0
- package/src/utils/semver.js +76 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{NAME}} Island
|
|
3
|
+
* Alpine.js interactive component
|
|
4
|
+
*/
|
|
5
|
+
import Alpine from 'alpinejs';
|
|
6
|
+
|
|
7
|
+
export default class {{NAME}}Island {
|
|
8
|
+
constructor(element, props = {}) {
|
|
9
|
+
this.element = element;
|
|
10
|
+
|
|
11
|
+
// Title aus props oder default
|
|
12
|
+
const title = props.title || '{{NAME}} Component';
|
|
13
|
+
|
|
14
|
+
// Create Alpine component HTML
|
|
15
|
+
const html = `
|
|
16
|
+
<div x-data="{ count: 0, doubled: 0 }" x-init="doubled = count * 2" class="p-6 bg-white rounded-lg border border-gray-200">
|
|
17
|
+
<h3 class="text-lg font-semibold mb-4">${title}</h3>
|
|
18
|
+
<p class="mb-2">Count: <span x-text="count"></span></p>
|
|
19
|
+
<p class="mb-4 text-gray-600">Doubled: <span x-text="doubled"></span></p>
|
|
20
|
+
<div class="flex gap-2">
|
|
21
|
+
<button @click="count--; doubled = count * 2" class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700">-</button>
|
|
22
|
+
<button @click="count++; doubled = count * 2" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">+</button>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
// Insert HTML into element
|
|
28
|
+
element.innerHTML = html;
|
|
29
|
+
|
|
30
|
+
// Initialize Alpine on this element if not already started
|
|
31
|
+
if (!window.Alpine) {
|
|
32
|
+
window.Alpine = Alpine;
|
|
33
|
+
Alpine.start();
|
|
34
|
+
} else {
|
|
35
|
+
// Initialize Alpine for this specific element
|
|
36
|
+
Alpine.initTree(element);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
destroy() {
|
|
41
|
+
// Alpine cleanup happens automatically
|
|
42
|
+
this.element.innerHTML = '';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{NAME}} Island
|
|
3
|
+
* Lit Web Component (without Shadow DOM for Tailwind support)
|
|
4
|
+
*/
|
|
5
|
+
import { LitElement, html } from 'lit';
|
|
6
|
+
|
|
7
|
+
class {{NAME}}Component extends LitElement {
|
|
8
|
+
static properties = {
|
|
9
|
+
title: { type: String },
|
|
10
|
+
count: { type: Number }
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Disable Shadow DOM to allow Tailwind classes to work
|
|
14
|
+
createRenderRoot() {
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
super();
|
|
20
|
+
this.title = '{{NAME}} Component';
|
|
21
|
+
this.count = 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
increment() {
|
|
25
|
+
this.count++;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
render() {
|
|
29
|
+
return html`
|
|
30
|
+
<div class="p-6 bg-white rounded-lg border border-gray-200">
|
|
31
|
+
<h3 class="text-lg font-semibold mb-4">${this.title}</h3>
|
|
32
|
+
<p class="mb-2">Count: ${this.count}</p>
|
|
33
|
+
<p class="mb-4 text-gray-600">Doubled: ${this.count * 2}</p>
|
|
34
|
+
<div class="flex gap-2">
|
|
35
|
+
<button @click=${() => this.count--} class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700">
|
|
36
|
+
-
|
|
37
|
+
</button>
|
|
38
|
+
<button @click=${this.increment} class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
|
|
39
|
+
+
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// For HMR: Use timestamp-based name to allow re-registration
|
|
48
|
+
const componentName = '{{KEBAB_NAME}}-component';
|
|
49
|
+
const timestamp = Date.now();
|
|
50
|
+
const uniqueComponentName = `${componentName}-${timestamp}`;
|
|
51
|
+
|
|
52
|
+
// Register custom element with unique name for HMR
|
|
53
|
+
if (!customElements.get(uniqueComponentName)) {
|
|
54
|
+
customElements.define(uniqueComponentName, {{NAME}}Component);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default class {{NAME}}Island {
|
|
58
|
+
constructor(element, props = {}) {
|
|
59
|
+
this.element = element;
|
|
60
|
+
|
|
61
|
+
// Replace element content with Lit component (use unique name)
|
|
62
|
+
const component = document.createElement(uniqueComponentName);
|
|
63
|
+
|
|
64
|
+
// Transfer any data-attributes
|
|
65
|
+
for (const attr of element.attributes) {
|
|
66
|
+
if (attr.name.startsWith('data-')) {
|
|
67
|
+
component.setAttribute(attr.name, attr.value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Set initial props
|
|
72
|
+
Object.assign(component, props);
|
|
73
|
+
|
|
74
|
+
element.appendChild(component);
|
|
75
|
+
this.component = component;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
destroy() {
|
|
79
|
+
// Remove component from DOM and clear element
|
|
80
|
+
if (this.component) {
|
|
81
|
+
if (this.component.parentNode) {
|
|
82
|
+
this.component.parentNode.removeChild(this.component);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Clear element content for clean re-initialization
|
|
86
|
+
if (this.element) {
|
|
87
|
+
this.element.innerHTML = '';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{NAME}} Island
|
|
3
|
+
* Preact interactive component
|
|
4
|
+
*/
|
|
5
|
+
import { render } from 'preact';
|
|
6
|
+
import { useState } from 'preact/hooks';
|
|
7
|
+
|
|
8
|
+
function {{NAME}}Component({ title = '{{NAME}} Component' }) {
|
|
9
|
+
const [count, setCount] = useState(0);
|
|
10
|
+
const doubled = count * 2;
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="p-6 bg-white rounded-lg border border-gray-200">
|
|
14
|
+
<h3 className="text-lg font-semibold mb-4">{title}</h3>
|
|
15
|
+
<p className="mb-2">Count: {count}</p>
|
|
16
|
+
<p className="mb-4 text-gray-600">Doubled: {doubled}</p>
|
|
17
|
+
<div className="flex gap-2">
|
|
18
|
+
<button
|
|
19
|
+
onClick={() => setCount(count - 1)}
|
|
20
|
+
className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700"
|
|
21
|
+
>
|
|
22
|
+
-
|
|
23
|
+
</button>
|
|
24
|
+
<button
|
|
25
|
+
onClick={() => setCount(count + 1)}
|
|
26
|
+
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
|
27
|
+
>
|
|
28
|
+
+
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default class {{NAME}}Island {
|
|
36
|
+
constructor(element, props = {}) {
|
|
37
|
+
this.element = element;
|
|
38
|
+
|
|
39
|
+
// Get initial data from data-attributes
|
|
40
|
+
const initialData = JSON.parse(element.dataset.initialData || '{}');
|
|
41
|
+
|
|
42
|
+
// Merge props
|
|
43
|
+
const allProps = { ...initialData, ...props };
|
|
44
|
+
|
|
45
|
+
render(<{{NAME}}Component {...allProps} />, element);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
destroy() {
|
|
49
|
+
// Preact will cleanup automatically
|
|
50
|
+
render(null, this.element);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{NAME}} Island
|
|
3
|
+
* React interactive component
|
|
4
|
+
*/
|
|
5
|
+
import React, { useState } from 'react';
|
|
6
|
+
import { createRoot } from 'react-dom/client';
|
|
7
|
+
|
|
8
|
+
function {{NAME}}Component({ title = '{{NAME}} Component' }) {
|
|
9
|
+
const [count, setCount] = useState(0);
|
|
10
|
+
const doubled = count * 2;
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="p-6 bg-white rounded-lg border border-gray-200">
|
|
14
|
+
<h3 className="text-lg font-semibold mb-4">{title}</h3>
|
|
15
|
+
<p className="mb-2">Count: {count}</p>
|
|
16
|
+
<p className="mb-4 text-gray-600">Doubled: {doubled}</p>
|
|
17
|
+
<div className="flex gap-2">
|
|
18
|
+
<button
|
|
19
|
+
onClick={() => setCount(count - 1)}
|
|
20
|
+
className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700"
|
|
21
|
+
>
|
|
22
|
+
-
|
|
23
|
+
</button>
|
|
24
|
+
<button
|
|
25
|
+
onClick={() => setCount(count + 1)}
|
|
26
|
+
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
|
27
|
+
>
|
|
28
|
+
+
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default class {{NAME}}Island {
|
|
36
|
+
constructor(element, props = {}) {
|
|
37
|
+
this.element = element;
|
|
38
|
+
this.root = createRoot(element);
|
|
39
|
+
|
|
40
|
+
// Get initial data from data-attributes
|
|
41
|
+
const initialData = JSON.parse(element.dataset.initialData || '{}');
|
|
42
|
+
const allProps = { ...initialData, ...props };
|
|
43
|
+
|
|
44
|
+
this.root.render(<{{NAME}}Component {...allProps} />);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
destroy() {
|
|
48
|
+
this.root?.unmount();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
// Props
|
|
3
|
+
let { title = '{{NAME}} Component' } = $props();
|
|
4
|
+
|
|
5
|
+
// Svelte 5 - Reactive state with $state() rune
|
|
6
|
+
let count = $state(0);
|
|
7
|
+
|
|
8
|
+
// Derived state with $derived() rune
|
|
9
|
+
let doubled = $derived(count * 2);
|
|
10
|
+
|
|
11
|
+
function increment() {
|
|
12
|
+
count += 1;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function decrement() {
|
|
16
|
+
count -= 1;
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<div class="p-6 bg-white rounded-lg border border-gray-200">
|
|
21
|
+
<h3 class="text-lg font-semibold mb-4">{title}</h3>
|
|
22
|
+
<p class="mb-2">Count: {count}</p>
|
|
23
|
+
<p class="mb-4 text-gray-600">Doubled: {doubled}</p>
|
|
24
|
+
<div class="flex gap-2">
|
|
25
|
+
<button onclick={decrement} class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700">
|
|
26
|
+
-
|
|
27
|
+
</button>
|
|
28
|
+
<button onclick={increment} class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
|
|
29
|
+
+
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<style>
|
|
35
|
+
/* Custom CSS falls benötigt */
|
|
36
|
+
</style>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{NAME}} Island
|
|
3
|
+
* Svelte 5 interactive component
|
|
4
|
+
*/
|
|
5
|
+
import Component from './{{NAME}}.svelte';
|
|
6
|
+
import { mount, unmount } from 'svelte';
|
|
7
|
+
|
|
8
|
+
export default class {{NAME}}Island {
|
|
9
|
+
constructor(element, props = {}) {
|
|
10
|
+
this.element = element;
|
|
11
|
+
|
|
12
|
+
// Get initial props from data-attributes
|
|
13
|
+
const initialData = JSON.parse(element.dataset.initialData || '{}');
|
|
14
|
+
|
|
15
|
+
// Merge props from constructor and data attributes
|
|
16
|
+
const allProps = { ...initialData, ...props };
|
|
17
|
+
|
|
18
|
+
// Svelte 5: Use mount() instead of new Component()
|
|
19
|
+
this.component = mount(Component, {
|
|
20
|
+
target: element,
|
|
21
|
+
props: allProps
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
destroy() {
|
|
26
|
+
// Svelte 5: Use unmount() instead of $destroy()
|
|
27
|
+
if (this.component) {
|
|
28
|
+
unmount(this.component);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{NAME}} Island
|
|
3
|
+
* Vanilla JavaScript interactive component
|
|
4
|
+
*/
|
|
5
|
+
export default class {{NAME}}Island {
|
|
6
|
+
constructor(element, props = {}) {
|
|
7
|
+
this.element = element;
|
|
8
|
+
this.props = props;
|
|
9
|
+
this.count = 0;
|
|
10
|
+
this.init();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
init() {
|
|
14
|
+
// Title aus props oder default
|
|
15
|
+
const title = this.props.title || '{{NAME}} Component';
|
|
16
|
+
|
|
17
|
+
// Create HTML structure
|
|
18
|
+
this.element.innerHTML = `
|
|
19
|
+
<div class="p-6 bg-white rounded-lg border border-gray-200">
|
|
20
|
+
<h3 class="text-lg font-semibold mb-4">${title}</h3>
|
|
21
|
+
<p class="mb-2">Count: <span class="count-display">0</span></p>
|
|
22
|
+
<p class="mb-4 text-gray-600">Doubled: <span class="doubled-display">0</span></p>
|
|
23
|
+
<div class="flex gap-2">
|
|
24
|
+
<button class="decrement-btn px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700">
|
|
25
|
+
-
|
|
26
|
+
</button>
|
|
27
|
+
<button class="increment-btn px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
|
|
28
|
+
+
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
// Get references to DOM elements
|
|
35
|
+
this.countDisplay = this.element.querySelector('.count-display');
|
|
36
|
+
this.doubledDisplay = this.element.querySelector('.doubled-display');
|
|
37
|
+
this.incrementBtn = this.element.querySelector('.increment-btn');
|
|
38
|
+
this.decrementBtn = this.element.querySelector('.decrement-btn');
|
|
39
|
+
|
|
40
|
+
// Add event listeners
|
|
41
|
+
this.incrementBtn.addEventListener('click', () => this.increment());
|
|
42
|
+
this.decrementBtn.addEventListener('click', () => this.decrement());
|
|
43
|
+
|
|
44
|
+
console.log('{{NAME}} Island initialized');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
increment() {
|
|
48
|
+
this.count++;
|
|
49
|
+
this.updateDisplay();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
decrement() {
|
|
53
|
+
this.count--;
|
|
54
|
+
this.updateDisplay();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
updateDisplay() {
|
|
58
|
+
this.countDisplay.textContent = this.count;
|
|
59
|
+
this.doubledDisplay.textContent = this.count * 2;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
destroy() {
|
|
63
|
+
// Remove event listeners
|
|
64
|
+
if (this.incrementBtn) {
|
|
65
|
+
this.incrementBtn.removeEventListener('click', this.increment);
|
|
66
|
+
}
|
|
67
|
+
if (this.decrementBtn) {
|
|
68
|
+
this.decrementBtn.removeEventListener('click', this.decrement);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{NAME}} Island
|
|
3
|
+
* Vue interactive component
|
|
4
|
+
*/
|
|
5
|
+
import { createApp } from 'vue';
|
|
6
|
+
|
|
7
|
+
const {{NAME}}Component = {
|
|
8
|
+
props: {
|
|
9
|
+
title: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: '{{NAME}} Component'
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
data() {
|
|
15
|
+
return {
|
|
16
|
+
count: 0
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
computed: {
|
|
20
|
+
doubled() {
|
|
21
|
+
return this.count * 2;
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
methods: {
|
|
25
|
+
increment() {
|
|
26
|
+
this.count++;
|
|
27
|
+
},
|
|
28
|
+
decrement() {
|
|
29
|
+
this.count--;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
template: `
|
|
33
|
+
<div class="p-6 bg-white rounded-lg border border-gray-200">
|
|
34
|
+
<h3 class="text-lg font-semibold mb-4">{{ title }}</h3>
|
|
35
|
+
<p class="mb-2">Count: {{ count }}</p>
|
|
36
|
+
<p class="mb-4 text-gray-600">Doubled: {{ doubled }}</p>
|
|
37
|
+
<div class="flex gap-2">
|
|
38
|
+
<button @click="decrement" class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700">-</button>
|
|
39
|
+
<button @click="increment" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">+</button>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
`
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default class {{NAME}}Island {
|
|
46
|
+
constructor(element, props = {}) {
|
|
47
|
+
this.element = element;
|
|
48
|
+
|
|
49
|
+
// Get initial data from data-attributes
|
|
50
|
+
const initialData = JSON.parse(element.dataset.initialData || '{}');
|
|
51
|
+
|
|
52
|
+
// Merge props
|
|
53
|
+
const allProps = { ...initialData, ...props };
|
|
54
|
+
|
|
55
|
+
// Create Vue app
|
|
56
|
+
this.app = createApp({{NAME}}Component, allProps);
|
|
57
|
+
this.instance = this.app.mount(element);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
destroy() {
|
|
61
|
+
if (this.app) {
|
|
62
|
+
this.app.unmount();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Auth Helper für CLI
|
|
7
|
+
* Speichert Login-Credentials in ~/.webmate/auth.json
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const AUTH_DIR = join(homedir(), '.webmate');
|
|
11
|
+
const AUTH_FILE = join(AUTH_DIR, 'auth.json');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Lädt gespeicherte Auth-Daten
|
|
15
|
+
*/
|
|
16
|
+
export function loadAuth() {
|
|
17
|
+
if (!existsSync(AUTH_FILE)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const content = readFileSync(AUTH_FILE, 'utf8');
|
|
23
|
+
if (!content || content.trim() === '') {
|
|
24
|
+
// Empty file - remove it
|
|
25
|
+
try {
|
|
26
|
+
unlinkSync(AUTH_FILE);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
// Ignore cleanup errors
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return JSON.parse(content);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
// Corrupted auth file - remove it
|
|
35
|
+
try {
|
|
36
|
+
unlinkSync(AUTH_FILE);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
// Ignore cleanup errors
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Speichert Auth-Daten
|
|
46
|
+
*/
|
|
47
|
+
export function saveAuth(authData) {
|
|
48
|
+
if (!existsSync(AUTH_DIR)) {
|
|
49
|
+
mkdirSync(AUTH_DIR, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
writeFileSync(AUTH_FILE, JSON.stringify(authData, null, 2), 'utf8');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Löscht gespeicherte Auth-Daten
|
|
57
|
+
*/
|
|
58
|
+
export function clearAuth() {
|
|
59
|
+
if (existsSync(AUTH_FILE)) {
|
|
60
|
+
writeFileSync(AUTH_FILE, '', 'utf8');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Prüft ob User eingeloggt ist
|
|
66
|
+
*/
|
|
67
|
+
export function isLoggedIn() {
|
|
68
|
+
const auth = loadAuth();
|
|
69
|
+
return auth && auth.user && auth.apiToken;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Gibt aktuellen User zurück
|
|
74
|
+
*/
|
|
75
|
+
export function getCurrentUser() {
|
|
76
|
+
const auth = loadAuth();
|
|
77
|
+
return auth?.user || null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Gibt API Token zurück
|
|
82
|
+
*/
|
|
83
|
+
export function getApiToken() {
|
|
84
|
+
const auth = loadAuth();
|
|
85
|
+
return auth?.apiToken || null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Gibt aktuellen Tenant zurück
|
|
90
|
+
*/
|
|
91
|
+
export function getCurrentTenant() {
|
|
92
|
+
const auth = loadAuth();
|
|
93
|
+
return auth?.tenant || null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Gibt CMS Base URL zurück
|
|
98
|
+
*/
|
|
99
|
+
export function getCmsBaseUrl() {
|
|
100
|
+
const auth = loadAuth();
|
|
101
|
+
if (!auth?.baseUrl) {
|
|
102
|
+
return 'https://app.localhost:3029'; // Default für Development
|
|
103
|
+
}
|
|
104
|
+
return auth.baseUrl;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Gibt Tenant CMS URL zurück
|
|
109
|
+
*/
|
|
110
|
+
export function getTenantCmsUrl() {
|
|
111
|
+
const tenant = getCurrentTenant();
|
|
112
|
+
const baseUrl = getCmsBaseUrl();
|
|
113
|
+
|
|
114
|
+
if (!tenant) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Extrahiere Domain aus Base URL (z.B. "app.localhost:3029" → "localhost:3029")
|
|
119
|
+
const url = new URL(baseUrl);
|
|
120
|
+
const parts = url.host.split('.');
|
|
121
|
+
const domain = parts.slice(1).join('.'); // Entferne "app" Subdomain
|
|
122
|
+
|
|
123
|
+
// Baue Tenant CMS URL
|
|
124
|
+
return `${url.protocol}//${tenant.subdomain}.cms.${domain}`;
|
|
125
|
+
}
|