@antelopejs/dms 0.16.0 → 0.16.2
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/CHANGELOG.md +43 -0
- package/README.md +14 -14
- package/dist/store/cache.d.ts +5 -1
- package/dist/store/cache.js +23 -14
- package/dist/store/cache.js.map +1 -1
- package/dist/store/frontend-compatibility.d.ts +3 -1
- package/dist/store/frontend-compatibility.js +6 -8
- package/dist/store/frontend-compatibility.js.map +1 -1
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.js +1 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/interfaces.d.ts +3 -2
- package/dist/store/interfaces.js +16 -14
- package/dist/store/interfaces.js.map +1 -1
- package/dist/store/listing.js +6 -4
- package/dist/store/listing.js.map +1 -1
- package/dist/store/operations.js +1 -1
- package/dist/store/operations.js.map +1 -1
- package/dist/store/registry.d.ts +2 -1
- package/dist/store/registry.js +7 -26
- package/dist/store/registry.js.map +1 -1
- package/dist/store/requirements.js +3 -3
- package/dist/store/requirements.js.map +1 -1
- package/frontend-vue/layers/dms-core/app/build/utils/storedAccounts.ts +62 -0
- package/frontend-vue/layers/dms-core/app/composables/auth/useMultiAccount.ts +12 -9
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +30 -3
- package/frontend-vue/layers/dms-layout/app/build/components/layout/HeaderActionIcon.vue +75 -0
- package/frontend-vue/layers/dms-layout/app/composables/useHeaderActions.ts +9 -0
- package/frontend-vue/layers/dms-layout/app/plugins/seo.ts +7 -0
- package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +10 -0
- package/frontend-vue/package.json +1 -1
- package/package.json +5 -5
- package/skills/dms-project/DEV-SERVERS.md +30 -4
- package/skills/dms-project/SKILL.md +8 -1
- package/skills/dms-project/scripts/dms-dev-usage.sh +5 -2
- package/skills/dms-project/scripts/dms-dev.sh +82 -69
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/store/registry.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/store/registry.ts"],"names":[],"mappings":";;;AAyBA,0BAiBC;AA4BD,sCAQC;AA9ED,2DAA8C;AAC9C,yCAAsC;AACtC,mCAA4D;AAG5D,MAAM,aAAa,GAAG,IAAA,qBAAS,EAAC,6BAAQ,CAAC,CAAC;AAE1C,MAAM,iBAAiB,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;AAC3E,MAAM,mBAAmB,GAAG,MAAM,CAAC;AACnC,MAAM,UAAU,GAAG,QAAQ,CAAC;AACf,QAAA,iBAAiB,GAAG,2BAA2B,CAAC;AAQ7D,MAAM,QAAQ,GAAG,IAAA,yBAAiB,EAAiB,iBAAiB,CAAC,CAAC;AAO/D,KAAK,UAAU,OAAO,CAC3B,IAAY,EACZ,MAAyB,EACzB,WAAmB;IAGnB,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CACpC,KAAK,EACL,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,QAAQ,CAAC,EACnC,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,mBAAmB,EAAE,CACnD,CAAC;QACF,MAAM,IAAI,GAAY,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACrE,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,UAAU,CACvB,WAAmB,EACnB,WAAmB;IAGnB,MAAM,IAAI,GAAG,CAAC,MAAM,OAAO,CACzB,GAAG,WAAW,IAAI,UAAU,EAAE,EAC9B,CAAC,SAAS,EAAE,cAAc,EAAE,YAAY,CAAC,EACzC,WAAW,CACZ,CAA8B,CAAC;IAChC,IAAI,CAAC,IAAI,EAAE,OAAO;QAAE,OAAO,SAAS,CAAC;IACrC,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,cAAc,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,yBAAiB,CAAC;QACtD,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,EAAE;QACrC,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC;YACpD,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC;YACxC,CAAC,CAAC,EAAE;KACP,CAAC;AACJ,CAAC;AAOD,SAAgB,aAAa,CAC3B,WAAmB,EACnB,WAAmB,EACnB,SAAoB;IAEpB,OAAO,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE;QAC3E,SAAS;KACV,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -19,7 +19,7 @@ function caretRange(release) {
|
|
|
19
19
|
}
|
|
20
20
|
async function assertFrontendSupports(context, release) {
|
|
21
21
|
const { package: packageName } = context.catalogEntry;
|
|
22
|
-
const range = await (0, frontend_compatibility_1.releaseFrontendRange)(packageName, release.version, context.projectPath);
|
|
22
|
+
const range = await (0, frontend_compatibility_1.releaseFrontendRange)(packageName, release.version, context.projectPath, "cached");
|
|
23
23
|
const running = (0, frontend_compatibility_1.runningFrontend)(context.projectPath);
|
|
24
24
|
if (!range || !running || (0, frontend_compatibility_1.supportsFrontend)(range, running.version))
|
|
25
25
|
return;
|
|
@@ -30,7 +30,7 @@ async function runningModules() {
|
|
|
30
30
|
}
|
|
31
31
|
async function pickDependencies(context, release, label) {
|
|
32
32
|
const implemented = await (0, interfaces_1.implementedInterfaces)(await runningModules());
|
|
33
|
-
const missing = await (0, interfaces_1.findMissingInterfaces)(release, implemented, context.projectPath);
|
|
33
|
+
const missing = await (0, interfaces_1.findMissingInterfaces)(release, implemented, context.projectPath, "cached");
|
|
34
34
|
const picked = new Map();
|
|
35
35
|
const unpicked = [];
|
|
36
36
|
for (const { interfacePackage } of missing) {
|
|
@@ -50,7 +50,7 @@ async function installDependency(context, interfacePackage, choice) {
|
|
|
50
50
|
if (await (0, declarations_1.hasDeclaration)(key)) {
|
|
51
51
|
throw new Error(`${key} is already in antelope.config.ts but does not run: start it from there`);
|
|
52
52
|
}
|
|
53
|
-
const release = await (0, registry_1.latestRelease)(key, context.projectPath,
|
|
53
|
+
const release = await (0, registry_1.latestRelease)(key, context.projectPath, "fresh");
|
|
54
54
|
if (!release) {
|
|
55
55
|
throw new Error(`The npm registry has no release of ${key}`);
|
|
56
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"requirements.js","sourceRoot":"","sources":["../../src/store/requirements.ts"],"names":[],"mappings":";;AAgDA,gCAEC;AAOD,
|
|
1
|
+
{"version":3,"file":"requirements.js","sourceRoot":"","sources":["../../src/store/requirements.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { StoredAccount } from "../../types/auth";
|
|
2
|
+
|
|
3
|
+
function lastUsedTime(account: StoredAccount): number {
|
|
4
|
+
return new Date(account.lastUsed).getTime();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function isRefused(account: StoredAccount): boolean {
|
|
8
|
+
return account.isExpired === true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Whether `account` is the better of two entries: live first, then newest. */
|
|
12
|
+
function isPreferred(account: StoredAccount, other: StoredAccount): boolean {
|
|
13
|
+
if (isRefused(account) !== isRefused(other)) return isRefused(other);
|
|
14
|
+
return lastUsedTime(account) > lastUsedTime(other);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Whether `stale` is an entry a recreated user record left behind: it can no
|
|
19
|
+
* longer sign in from this browser (marked expired), and a better entry holds
|
|
20
|
+
* its email under another user id. A live entry is never one, since a stored
|
|
21
|
+
* email can be out of date and name another person.
|
|
22
|
+
*/
|
|
23
|
+
function isRecreatedAs(stale: StoredAccount, current: StoredAccount): boolean {
|
|
24
|
+
return (
|
|
25
|
+
stale.userId !== current.userId &&
|
|
26
|
+
stale.email === current.email &&
|
|
27
|
+
isRefused(stale) &&
|
|
28
|
+
isPreferred(current, stale)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isReplacedBy(stored: StoredAccount, account: StoredAccount): boolean {
|
|
33
|
+
return stored.userId === account.userId || isRecreatedAs(stored, account);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** `accounts` with `account` in place of every entry it replaces. */
|
|
37
|
+
export function withAccount(
|
|
38
|
+
accounts: StoredAccount[],
|
|
39
|
+
account: StoredAccount,
|
|
40
|
+
): StoredAccount[] {
|
|
41
|
+
const index = accounts.findIndex((stored) => isReplacedBy(stored, account));
|
|
42
|
+
const others = accounts.filter((stored) => !isReplacedBy(stored, account));
|
|
43
|
+
if (index < 0) return [...others, account];
|
|
44
|
+
return [...others.slice(0, index), account, ...others.slice(index)];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* One entry per user, the preferred one, without the refused entries of user
|
|
49
|
+
* records recreated under the same email.
|
|
50
|
+
*/
|
|
51
|
+
export function uniqueAccounts(accounts: StoredAccount[]): StoredAccount[] {
|
|
52
|
+
const byUser = new Map<string, StoredAccount>();
|
|
53
|
+
for (const account of accounts) {
|
|
54
|
+
const kept = byUser.get(account.userId);
|
|
55
|
+
if (!kept || isPreferred(account, kept))
|
|
56
|
+
byUser.set(account.userId, account);
|
|
57
|
+
}
|
|
58
|
+
const perUser = [...byUser.values()];
|
|
59
|
+
return perUser.filter(
|
|
60
|
+
(account) => !perUser.some((other) => isRecreatedAs(account, other)),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
SwitchAccountResponse,
|
|
5
5
|
ValidateAccountResponse,
|
|
6
6
|
} from "../../types/auth";
|
|
7
|
+
import { uniqueAccounts, withAccount } from "../../build/utils/storedAccounts";
|
|
7
8
|
|
|
8
9
|
const STORAGE_KEY = "antelopejs-accounts";
|
|
9
10
|
const SWITCH_ACCOUNT_ENDPOINT = "/auth/switch-account";
|
|
@@ -14,6 +15,12 @@ type AccountsRef = ReturnType<typeof useStorage<StoredAccount[]>>;
|
|
|
14
15
|
|
|
15
16
|
const accountsStorageByApp = new WeakMap<object, AccountsRef>();
|
|
16
17
|
|
|
18
|
+
function dropDuplicates(accounts: AccountsRef) {
|
|
19
|
+
if (!Array.isArray(accounts.value)) return;
|
|
20
|
+
const unique = uniqueAccounts(accounts.value);
|
|
21
|
+
if (unique.length < accounts.value.length) accounts.value = unique;
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
/**
|
|
18
25
|
* The stored accounts, on a ref whose persistence outlives any component.
|
|
19
26
|
*
|
|
@@ -34,6 +41,7 @@ function useAccountsStorage(): AccountsRef {
|
|
|
34
41
|
const storage = effectScope(true).run(() =>
|
|
35
42
|
useStorage<StoredAccount[]>(STORAGE_KEY, [], undefined),
|
|
36
43
|
) as AccountsRef;
|
|
44
|
+
dropDuplicates(storage);
|
|
37
45
|
accountsStorageByApp.set(dmsApp, storage);
|
|
38
46
|
return storage;
|
|
39
47
|
}
|
|
@@ -88,20 +96,13 @@ function upsertAccount(
|
|
|
88
96
|
user: MinimalUser,
|
|
89
97
|
accountId: string,
|
|
90
98
|
) {
|
|
91
|
-
|
|
92
|
-
const account: StoredAccount = {
|
|
99
|
+
accounts.value = withAccount(accounts.value, {
|
|
93
100
|
accountId,
|
|
94
101
|
userId: user._id,
|
|
95
102
|
email: user.email,
|
|
96
103
|
name: user.name,
|
|
97
104
|
lastUsed: new Date(),
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
if (existingIndex >= 0) {
|
|
101
|
-
accounts.value[existingIndex] = account;
|
|
102
|
-
} else {
|
|
103
|
-
accounts.value.push(account);
|
|
104
|
-
}
|
|
105
|
+
});
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
async function requestAccountSwitch(account: StoredAccount) {
|
|
@@ -168,6 +169,7 @@ function createSwitchAccount(
|
|
|
168
169
|
} catch (error) {
|
|
169
170
|
if (isTokenRejection(error)) {
|
|
170
171
|
account.isExpired = true;
|
|
172
|
+
dropDuplicates(accounts);
|
|
171
173
|
}
|
|
172
174
|
throw error;
|
|
173
175
|
}
|
|
@@ -187,6 +189,7 @@ function createValidateAllAccounts(
|
|
|
187
189
|
.filter((a) => a.userId !== currentUserId)
|
|
188
190
|
.map((account) => validateAccount(account)),
|
|
189
191
|
);
|
|
192
|
+
dropDuplicates(accounts);
|
|
190
193
|
};
|
|
191
194
|
}
|
|
192
195
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { BreadcrumbItem, DropdownMenuItem } from "@nuxt/ui";
|
|
3
3
|
import { onMounted, ref } from "vue";
|
|
4
4
|
import NotificationPopover from "../notification/NotificationPopover.vue";
|
|
5
|
+
import HeaderActionIcon from "./HeaderActionIcon.vue";
|
|
5
6
|
import QuickActionsPopover from "./QuickActionsPopover.vue";
|
|
6
7
|
import { useSidebarState } from "./sidebarState";
|
|
7
8
|
import {
|
|
@@ -24,6 +25,7 @@ const MOBILE_ELLIPSIS_SLOT = "ellipsis";
|
|
|
24
25
|
/** v2 toolbar buttons: muted until hovered or open. */
|
|
25
26
|
const TOOLBAR_BUTTON_CLASS =
|
|
26
27
|
"text-muted hover:text-highlighted data-[state=open]:text-highlighted";
|
|
28
|
+
const BUILDER_FALLBACK_ICON = "i-ph-hammer-light";
|
|
27
29
|
/** The crumb naming a row keeps the row's own case (crumbs capitalize). */
|
|
28
30
|
const RECORD_CRUMB_LABEL_CLASS = "first-letter:normal-case";
|
|
29
31
|
|
|
@@ -50,6 +52,17 @@ const registeredActions = computed(() =>
|
|
|
50
52
|
: visibleActions.value.filter(isPlainDataHeaderAction),
|
|
51
53
|
);
|
|
52
54
|
|
|
55
|
+
/** The component of each action whose `iconComponent` resolves, by action id. */
|
|
56
|
+
const iconComponents = computed(
|
|
57
|
+
() =>
|
|
58
|
+
new Map(
|
|
59
|
+
registeredActions.value.flatMap((action) => {
|
|
60
|
+
const component = resolveDmsComponent(action.iconComponent);
|
|
61
|
+
return component ? [[action.id, component] as const] : [];
|
|
62
|
+
}),
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
|
|
53
66
|
const builderAction = computed(() =>
|
|
54
67
|
registeredActions.value.find((action) => action.id === BUILDER_ACTION_ID),
|
|
55
68
|
);
|
|
@@ -290,7 +303,7 @@ const mobileBreadcrumb = computed(
|
|
|
290
303
|
:content="TOOLTIP_CONTENT"
|
|
291
304
|
>
|
|
292
305
|
<UButton
|
|
293
|
-
:icon="builderAction.icon ||
|
|
306
|
+
:icon="builderAction.icon || BUILDER_FALLBACK_ICON"
|
|
294
307
|
variant="ghost"
|
|
295
308
|
:color="isActionActive(builderAction) ? 'primary' : 'neutral'"
|
|
296
309
|
:class="
|
|
@@ -304,7 +317,14 @@ const mobileBreadcrumb = computed(
|
|
|
304
317
|
"
|
|
305
318
|
:ui="{ leadingIcon: 'size-[18px]' }"
|
|
306
319
|
@click="selectAction(builderAction)"
|
|
307
|
-
|
|
320
|
+
>
|
|
321
|
+
<template v-if="iconComponents.has(builderAction.id)" #leading>
|
|
322
|
+
<HeaderActionIcon
|
|
323
|
+
:component="iconComponents.get(builderAction.id)"
|
|
324
|
+
:icon="builderAction.icon || BUILDER_FALLBACK_ICON"
|
|
325
|
+
/>
|
|
326
|
+
</template>
|
|
327
|
+
</UButton>
|
|
308
328
|
</UTooltip>
|
|
309
329
|
|
|
310
330
|
<UTooltip
|
|
@@ -324,7 +344,14 @@ const mobileBreadcrumb = computed(
|
|
|
324
344
|
"
|
|
325
345
|
:ui="{ leadingIcon: 'size-[18px]' }"
|
|
326
346
|
@click="selectAction(action)"
|
|
327
|
-
|
|
347
|
+
>
|
|
348
|
+
<template v-if="iconComponents.has(action.id)" #leading>
|
|
349
|
+
<HeaderActionIcon
|
|
350
|
+
:component="iconComponents.get(action.id)"
|
|
351
|
+
:icon="action.icon"
|
|
352
|
+
/>
|
|
353
|
+
</template>
|
|
354
|
+
</UButton>
|
|
328
355
|
</UTooltip>
|
|
329
356
|
|
|
330
357
|
<QuickActionsPopover />
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
getCurrentInstance,
|
|
4
|
+
onErrorCaptured,
|
|
5
|
+
onMounted,
|
|
6
|
+
ref,
|
|
7
|
+
watch,
|
|
8
|
+
type Component,
|
|
9
|
+
type ComponentInternalInstance,
|
|
10
|
+
} from "vue";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Draws a header action's `iconComponent` in the leading slot of its button.
|
|
14
|
+
* `icon` stands in, at the size of the button's own leading icon, while there
|
|
15
|
+
* is no component, on the server and the hydration pass, until the component
|
|
16
|
+
* has rendered, and when it fails to load. A new `component` gets its own
|
|
17
|
+
* attempt after a failed one.
|
|
18
|
+
*/
|
|
19
|
+
const props = defineProps<{
|
|
20
|
+
component?: Component;
|
|
21
|
+
icon: string;
|
|
22
|
+
}>();
|
|
23
|
+
|
|
24
|
+
/** The box of the `leadingIcon` this replaces, whichever of the two draws. */
|
|
25
|
+
const ICON_CLASS = "size-[18px] shrink-0";
|
|
26
|
+
|
|
27
|
+
const mounted = ref(false);
|
|
28
|
+
onMounted(() => {
|
|
29
|
+
mounted.value = true;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const failed = ref(false);
|
|
33
|
+
watch(
|
|
34
|
+
() => props.component,
|
|
35
|
+
() => {
|
|
36
|
+
failed.value = false;
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
const self = getCurrentInstance();
|
|
40
|
+
|
|
41
|
+
/** The component drawn here that `source` belongs to: its ancestor whose parent is this one. */
|
|
42
|
+
function drawnComponentOf(
|
|
43
|
+
source: ComponentInternalInstance | null,
|
|
44
|
+
): ComponentInternalInstance | null {
|
|
45
|
+
let current = source;
|
|
46
|
+
while (current && current.parent !== self) current = current.parent;
|
|
47
|
+
return current;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
onErrorCaptured((error, instance) => {
|
|
51
|
+
if (drawnComponentOf(instance?.$ ?? null)?.type !== props.component)
|
|
52
|
+
return false;
|
|
53
|
+
failed.value = true;
|
|
54
|
+
if (import.meta.env.DEV)
|
|
55
|
+
console.warn(
|
|
56
|
+
"[dms] header action icon component failed to load; drawing icon instead",
|
|
57
|
+
error,
|
|
58
|
+
);
|
|
59
|
+
return false;
|
|
60
|
+
});
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<UIcon
|
|
65
|
+
v-if="!mounted || failed || !component"
|
|
66
|
+
:name="icon"
|
|
67
|
+
:class="ICON_CLASS"
|
|
68
|
+
/>
|
|
69
|
+
<Suspense v-else>
|
|
70
|
+
<component :is="component" :class="ICON_CLASS" />
|
|
71
|
+
<template #fallback>
|
|
72
|
+
<UIcon :name="icon" :class="ICON_CLASS" />
|
|
73
|
+
</template>
|
|
74
|
+
</Suspense>
|
|
75
|
+
</template>
|
|
@@ -9,6 +9,15 @@ import { isFunction, isString } from "#dms-core/app/utils/type-check";
|
|
|
9
9
|
export interface HeaderAction {
|
|
10
10
|
id: string;
|
|
11
11
|
icon: string;
|
|
12
|
+
/**
|
|
13
|
+
* A component drawn in place of `icon`, by the name `registerComponent` gave
|
|
14
|
+
* it (resolved as a side panel's `component` is), for an icon that animates
|
|
15
|
+
* or follows a state. The component is drawn once the app is mounted. `icon`
|
|
16
|
+
* stays required: it is drawn on the server's first paint, while the name
|
|
17
|
+
* does not resolve, while an async component loads, and when it fails to
|
|
18
|
+
* load. The name is plain data, so a server still renders the action.
|
|
19
|
+
*/
|
|
20
|
+
iconComponent?: string;
|
|
12
21
|
/** Tooltip and accessible name: a plain literal, or an i18n key when prefixed with "$". */
|
|
13
22
|
label: string;
|
|
14
23
|
/** Run on click. Required unless `sidePanelId` is set, which it overrides. */
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { watch } from "vue";
|
|
2
|
+
|
|
1
3
|
export default defineDmsPlugin(async () => {
|
|
2
4
|
const { metaTitle, metaDescription, refresh } = useSystemState();
|
|
3
5
|
|
|
@@ -25,5 +27,10 @@ export default defineDmsPlugin(async () => {
|
|
|
25
27
|
],
|
|
26
28
|
});
|
|
27
29
|
|
|
30
|
+
if (typeof window !== "undefined")
|
|
31
|
+
watch(metaTitle, (title) => {
|
|
32
|
+
if (!title) void refresh();
|
|
33
|
+
});
|
|
34
|
+
|
|
28
35
|
if (!metaTitle.value) await refresh();
|
|
29
36
|
});
|
|
@@ -191,10 +191,20 @@ const toastTheme = {
|
|
|
191
191
|
* floating bar, a drawer's footer band): the bar sets how far it reaches
|
|
192
192
|
* (`--dms-toaster-clearance`, see `toasterClearance.ts`), so a toast never
|
|
193
193
|
* covers its buttons, and the stack drops back once the bar hides.
|
|
194
|
+
*
|
|
195
|
+
* reka-ui 2.11 writes its own `data-expanded` on each toast over Nuxt UI's
|
|
196
|
+
* `true`/`false`, from its own hover and focus state, so Nuxt UI's collapsed
|
|
197
|
+
* rules on the toast (`data-[expanded=false]`) never match. The viewport keeps
|
|
198
|
+
* Nuxt UI's attribute, and `in-data-[expanded=false]` restates those rules from
|
|
199
|
+
* it: a toast behind the front one takes the front one's height and hides its
|
|
200
|
+
* content, and when it closes it fades out where it sits. Without that, it ran
|
|
201
|
+
* the expanded exit, which moves it to its expanded slot, well above the
|
|
202
|
+
* collapsed stack.
|
|
194
203
|
*/
|
|
195
204
|
const toasterTheme = {
|
|
196
205
|
slots: {
|
|
197
206
|
viewport: "w-[calc(100%-2.5rem)] sm:w-[380px]",
|
|
207
|
+
base: "in-data-[expanded=false]:data-[front=false]:h-(--front-height) in-data-[expanded=false]:data-[front=false]:*:opacity-0 data-[state=closed]:in-data-[expanded=false]:data-[front=false]:animate-[fade-out_200ms_ease-in-out]",
|
|
198
208
|
},
|
|
199
209
|
variants: {
|
|
200
210
|
position: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/dms",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
4
4
|
"description": "AntelopeJS DMS. Plug in your PostgreSQL, MongoDB or RethinkDB database and your coding agent builds a back office and analytics dashboard as typed TypeScript. Real-time updates on the data the DMS manages.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelopejs",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"zod": "~3.24.2"
|
|
94
94
|
},
|
|
95
95
|
"devDependencies": {
|
|
96
|
-
"@antelopejs/core": ">=1.
|
|
96
|
+
"@antelopejs/core": ">=1.20.0 <2",
|
|
97
97
|
"@antelopejs/tooling-configs": "0.2.18",
|
|
98
98
|
"@types/archiver": "^6.0.4",
|
|
99
99
|
"@types/chai": "^4.3.20",
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
"tsc-alias": "^1.9.1",
|
|
119
119
|
"typescript": "^6.0.3",
|
|
120
120
|
"vue": "^3.5.18",
|
|
121
|
-
"@antelopejs/api": "
|
|
121
|
+
"@antelopejs/api": "^1.7.0"
|
|
122
122
|
},
|
|
123
123
|
"exports": {
|
|
124
124
|
".": {
|
|
@@ -138,11 +138,11 @@
|
|
|
138
138
|
"registry": "https://registry.npmjs.org/"
|
|
139
139
|
},
|
|
140
140
|
"optionalDependencies": {
|
|
141
|
-
"@antelopejs/interface-dms-automation": "
|
|
141
|
+
"@antelopejs/interface-dms-automation": ">=0.6.0 <1.0.0",
|
|
142
142
|
"@antelopejs/interface-redis": ">=0.0.9 <1.0.0"
|
|
143
143
|
},
|
|
144
144
|
"peerDependencies": {
|
|
145
|
-
"@antelopejs/core": ">=1.
|
|
145
|
+
"@antelopejs/core": ">=1.20.0 <2"
|
|
146
146
|
},
|
|
147
147
|
"peerDependenciesMeta": {
|
|
148
148
|
"@antelopejs/core": {
|
|
@@ -36,10 +36,7 @@ overwrite the memory.
|
|
|
36
36
|
|
|
37
37
|
## Choose the process manager
|
|
38
38
|
|
|
39
|
-
The script is for local shells.
|
|
40
|
-
`amp orb services ensure`, or `amp orb service start` for a one-off service; check readiness with
|
|
41
|
-
service logs and HTTP checks and share the returned portal URL. Processes the script detaches do
|
|
42
|
-
not survive orb updates.
|
|
39
|
+
The script is for local shells.
|
|
43
40
|
|
|
44
41
|
## What `stop` and `restart` touch
|
|
45
42
|
|
|
@@ -55,6 +52,35 @@ the processes of that server alone that the scan below finds. The other server k
|
|
|
55
52
|
|
|
56
53
|
Run one `start` or `restart` at a time: a second concurrent call hits a port or lock conflict.
|
|
57
54
|
|
|
55
|
+
## Several worktrees side by side
|
|
56
|
+
|
|
57
|
+
Each project keeps its own pid file and logs, so projects only collide on ports, MongoDB and
|
|
58
|
+
temporary files. `antelopejs-lease` (in the project's tooling-configs) hands a command its own set
|
|
59
|
+
of those. Prefix the script with it:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
DMS_BACK_DIR=<resolved-path> pnpm exec antelopejs-lease run -- bash "<skill-base-dir>/scripts/dms-dev.sh" start
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The lease sets `DMS_BACK_PORT` and `DMS_FRONT_PORT`, never `PORT`. The script turns each into
|
|
66
|
+
`PORT` for its own process only, because `ajs dms dev` and the DMS playground's
|
|
67
|
+
`antelope.config.ts` read `PORT` and would otherwise share one port. Without the variables nothing
|
|
68
|
+
changes: no `PORT` is set and the reported URLs default to `5010` and `3001`; with them, to those
|
|
69
|
+
ports.
|
|
70
|
+
|
|
71
|
+
Pass `DMS_BACK_CMD` / `DMS_FRONT_CMD` overrides through the environment, not as arguments of a wrapper command (`antelopejs-lease run -- env DMS_BACK_CMD=… bash dms-dev.sh start`): the script's process search can otherwise match the wrapper's command line.
|
|
72
|
+
|
|
73
|
+
After the backend's ready line, `start` also waits (within `DMS_DEV_TIMEOUT`) until `GET /dms/frontend?renderer=vue&rendererVersion=3`, the request the dashboard makes first, answers 200, 401 or 403. The ready line can print before the modules have loaded, and the manifest answers 503 until they have, which made the dashboard exit when several backends started at once. The wait is skipped without `curl`, without a `.antelope/dev.json`, or when nothing accepts connections on the recorded port.
|
|
74
|
+
|
|
75
|
+
A config that hard-codes the api port (most DMS repos pin `5010`) ignores `PORT`. Two such projects
|
|
76
|
+
still start side by side: in dev mode the second backend listens on the next free port and logs
|
|
77
|
+
`Port <n> in use, listening on <url> instead.`, and the script treats that line as ready. The
|
|
78
|
+
backend URL it reports comes from `.antelope/dev.json`, which records the port actually bound, and
|
|
79
|
+
the dashboard finds the backend through that same file.
|
|
80
|
+
|
|
81
|
+
Logs default to `<DMS_BACK_DIR>/.antelope/logs/back.log` and `front.log` (ignored by git with the
|
|
82
|
+
rest of `.antelope`); `status` prints both paths. `DMS_BACK_LOG` / `DMS_FRONT_LOG` override them.
|
|
83
|
+
|
|
58
84
|
## Windows
|
|
59
85
|
|
|
60
86
|
The script runs in Git Bash, MSYS2 or Cygwin. Their `ps` lists only their own processes and takes
|
|
@@ -205,7 +205,7 @@ DMS_BACK_DIR=<resolved-path> bash "<skill-base-dir>/scripts/dms-dev.sh" restart
|
|
|
205
205
|
| `start` | Nothing is running (fresh shell, after `stop`). |
|
|
206
206
|
| `stop` | Before walking away, before destructive operations, on session cleanup. |
|
|
207
207
|
| `status` | The user asks whether the DMS is running. |
|
|
208
|
-
| `back-log [N]` / `front-log [N]` | A boot fails: read the last N (default 60) lines of
|
|
208
|
+
| `back-log [N]` / `front-log [N]` | A boot fails: read the last N (default 60) lines of `<DMS_BACK_DIR>/.antelope/logs/back.log` / `front.log`. |
|
|
209
209
|
|
|
210
210
|
The call returns once both servers print their ready line and reports the URLs they listen on; it
|
|
211
211
|
fails fast when a server exits early, otherwise after 180 s per server (`DMS_DEV_TIMEOUT`). The
|
|
@@ -214,6 +214,13 @@ servers keep running detached. By default the script runs the project's `pnpm de
|
|
|
214
214
|
override them with `DMS_BACK_CMD` / `DMS_FRONT_CMD`. It runs in bash on Linux and macOS, and in
|
|
215
215
|
Git Bash, MSYS2 or Cygwin on Windows ([DEV-SERVERS.md](DEV-SERVERS.md#windows)).
|
|
216
216
|
|
|
217
|
+
Several worktrees or repos run side by side when each call gets its own ports: prefix the script
|
|
218
|
+
with `pnpm exec antelopejs-lease run --` where the project's tooling-configs has it. The script
|
|
219
|
+
hands `DMS_BACK_PORT` / `DMS_FRONT_PORT` to the backend / frontend process as its `PORT`, and
|
|
220
|
+
logs under the project's own `.antelope/logs/`. A backend whose config pins the api port still
|
|
221
|
+
starts: it falls back to the next free port and the script reports that one
|
|
222
|
+
([DEV-SERVERS.md](DEV-SERVERS.md#several-worktrees-side-by-side)).
|
|
223
|
+
|
|
217
224
|
When you hand off for manual testing, run `restart` yourself, wait for both ready lines, then tell
|
|
218
225
|
the user what to click. Boot failures, stale frontends, a refused credential (401), stop scope and
|
|
219
226
|
managed environments (Amp orbs): [DEV-SERVERS.md](DEV-SERVERS.md).
|
|
@@ -22,8 +22,11 @@ Environment:
|
|
|
22
22
|
the playground convention for "ajs project dev -w" / legacy "run -w").
|
|
23
23
|
DMS_FRONT_CMD Command to start the frontend, run in DMS_BACK_DIR (default:
|
|
24
24
|
"pnpm frontend:dev", the playground convention for "ajs dms dev").
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
DMS_BACK_PORT Port of the backend: it becomes PORT for the backend process only, and the
|
|
26
|
+
default of the URL reported when the backend gives none (default: unset).
|
|
27
|
+
DMS_FRONT_PORT The same for the frontend (default: unset).
|
|
28
|
+
DMS_BACK_LOG (default: <DMS_BACK_DIR>/.antelope/logs/back.log)
|
|
29
|
+
DMS_FRONT_LOG (default: <DMS_BACK_DIR>/.antelope/logs/front.log)
|
|
27
30
|
DMS_DEV_TIMEOUT (default: 180 seconds per server)
|
|
28
31
|
DMS_DEV_STOP_GRACE (default: 10 seconds between SIGTERM and SIGKILL; unused on Windows)
|
|
29
32
|
EOF
|