sh3-core 0.13.1 → 0.13.3
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/dist/BrandSlot.svelte +62 -13
- package/dist/__test__/setup-dom.js +5 -0
- package/dist/actions/MenuButton.svelte +2 -1
- package/dist/actions/contextMenuModel.d.ts +1 -1
- package/dist/actions/contextMenuModel.js +2 -1
- package/dist/actions/dispatcher.svelte.d.ts +1 -1
- package/dist/actions/dispatcher.svelte.js +2 -1
- package/dist/actions/listActive.d.ts +1 -1
- package/dist/actions/listActive.js +2 -1
- package/dist/actions/listeners.d.ts +1 -1
- package/dist/actions/listeners.js +6 -5
- package/dist/actions/menuBarModel.js +3 -2
- package/dist/actions/paletteModel.js +2 -1
- package/dist/actions/resolveLabel.test.js +14 -0
- package/dist/actions/types.d.ts +12 -1
- package/dist/actions/types.js +7 -1
- package/dist/api.d.ts +3 -0
- package/dist/api.js +3 -0
- package/dist/app/store/AppUpdateAvailableModal.svelte +87 -0
- package/dist/app/store/AppUpdateAvailableModal.svelte.d.ts +11 -0
- package/dist/app/store/InstalledView.svelte +8 -54
- package/dist/app/store/UninstallAppDialog.svelte +86 -0
- package/dist/app/store/UninstallAppDialog.svelte.d.ts +10 -0
- package/dist/app/store/permissionConfirm.d.ts +4 -0
- package/dist/app/store/permissionConfirm.js +28 -0
- package/dist/app/store/storeShard.svelte.d.ts +8 -1
- package/dist/app/store/storeShard.svelte.js +42 -9
- package/dist/app/store/updatePackage.test.d.ts +1 -0
- package/dist/app/store/updatePackage.test.js +34 -0
- package/dist/app/store/verbs.d.ts +1 -0
- package/dist/app/store/verbs.js +79 -5
- package/dist/app/store/verbs.test.d.ts +1 -0
- package/dist/app/store/verbs.test.js +56 -0
- package/dist/app-appearance/AppAppearanceModal.svelte +174 -0
- package/dist/app-appearance/AppAppearanceModal.svelte.d.ts +8 -0
- package/dist/app-appearance/appearanceShard.svelte.d.ts +2 -0
- package/dist/app-appearance/appearanceShard.svelte.js +61 -0
- package/dist/app-appearance/appearanceState.svelte.d.ts +15 -0
- package/dist/app-appearance/appearanceState.svelte.js +59 -0
- package/dist/app-appearance/appearanceState.test.d.ts +1 -0
- package/dist/app-appearance/appearanceState.test.js +30 -0
- package/dist/app-appearance/index.d.ts +3 -0
- package/dist/app-appearance/index.js +2 -0
- package/dist/app-appearance/types.d.ts +11 -0
- package/dist/app-appearance/types.js +1 -0
- package/dist/apps/lifecycle.js +10 -2
- package/dist/apps/types.d.ts +18 -4
- package/dist/apps/workspace-rekey.d.ts +1 -0
- package/dist/apps/workspace-rekey.js +35 -0
- package/dist/apps/workspace-rekey.test.d.ts +1 -0
- package/dist/apps/workspace-rekey.test.js +23 -0
- package/dist/assets/iconIds.generated.d.ts +2 -0
- package/dist/assets/iconIds.generated.js +154 -0
- package/dist/auth/admin-users.svelte.d.ts +9 -0
- package/dist/auth/admin-users.svelte.js +42 -0
- package/dist/auth/admin-users.test.d.ts +1 -0
- package/dist/auth/admin-users.test.js +52 -0
- package/dist/createShell.js +5 -5
- package/dist/documents/config.d.ts +5 -1
- package/dist/documents/config.js +16 -8
- package/dist/documents/index.d.ts +1 -1
- package/dist/documents/index.js +1 -1
- package/dist/host-entry.d.ts +1 -1
- package/dist/host-entry.js +1 -1
- package/dist/host.d.ts +1 -1
- package/dist/host.js +9 -2
- package/dist/primitives/Button.svelte +50 -4
- package/dist/primitives/Button.svelte.d.ts +3 -1
- package/dist/primitives/Collapsible.svelte +110 -0
- package/dist/primitives/Collapsible.svelte.d.ts +14 -0
- package/dist/primitives/widgets/AppPicker.svelte +41 -0
- package/dist/primitives/widgets/AppPicker.svelte.d.ts +9 -0
- package/dist/primitives/widgets/AppPicker.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/AppPicker.svelte.test.js +26 -0
- package/dist/primitives/widgets/AppPicker.test.d.ts +1 -0
- package/dist/primitives/widgets/AppPicker.test.js +74 -0
- package/dist/primitives/widgets/ColorSwatch.svelte +7 -2
- package/dist/primitives/widgets/ColorSwatch.svelte.d.ts +2 -1
- package/dist/primitives/widgets/ColorSwatch.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/ColorSwatch.svelte.test.js +31 -0
- package/dist/primitives/widgets/Field.svelte +4 -2
- package/dist/primitives/widgets/Field.svelte.d.ts +2 -2
- package/dist/primitives/widgets/Field.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/Field.svelte.test.js +33 -0
- package/dist/primitives/widgets/FilePicker.svelte +2 -2
- package/dist/primitives/widgets/FilePicker.svelte.d.ts +2 -2
- package/dist/primitives/widgets/FilePicker.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/FilePicker.svelte.test.js +31 -0
- package/dist/primitives/widgets/IconPicker.svelte +115 -0
- package/dist/primitives/widgets/IconPicker.svelte.d.ts +9 -0
- package/dist/primitives/widgets/IconPicker.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/IconPicker.svelte.test.js +43 -0
- package/dist/primitives/widgets/IconToggleGroup.svelte +4 -4
- package/dist/primitives/widgets/IconToggleGroup.svelte.d.ts +3 -3
- package/dist/primitives/widgets/IconToggleGroup.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/IconToggleGroup.svelte.test.js +40 -0
- package/dist/primitives/widgets/NumberInput.svelte +19 -9
- package/dist/primitives/widgets/NumberInput.svelte.d.ts +2 -2
- package/dist/primitives/widgets/NumberInput.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/NumberInput.svelte.test.js +48 -0
- package/dist/primitives/widgets/PickerList.d.ts +24 -0
- package/dist/primitives/widgets/PickerList.js +21 -0
- package/dist/primitives/widgets/PickerList.svelte +150 -0
- package/dist/primitives/widgets/PickerList.svelte.d.ts +16 -0
- package/dist/primitives/widgets/PickerList.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/PickerList.svelte.test.js +31 -0
- package/dist/primitives/widgets/PickerList.test.d.ts +1 -0
- package/dist/primitives/widgets/PickerList.test.js +218 -0
- package/dist/primitives/widgets/RangeSlider.svelte +11 -4
- package/dist/primitives/widgets/RangeSlider.svelte.d.ts +2 -2
- package/dist/primitives/widgets/RangeSlider.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/RangeSlider.svelte.test.js +38 -0
- package/dist/primitives/widgets/Segmented.svelte +4 -4
- package/dist/primitives/widgets/Segmented.svelte.d.ts +3 -3
- package/dist/primitives/widgets/Segmented.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/Segmented.svelte.test.js +25 -0
- package/dist/primitives/widgets/Select.svelte +4 -4
- package/dist/primitives/widgets/Select.svelte.d.ts +3 -3
- package/dist/primitives/widgets/Select.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/Select.svelte.test.js +37 -0
- package/dist/primitives/widgets/Slider.svelte +4 -2
- package/dist/primitives/widgets/Slider.svelte.d.ts +2 -2
- package/dist/primitives/widgets/Slider.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/Slider.svelte.test.js +22 -0
- package/dist/primitives/widgets/SliderGroup.svelte +4 -2
- package/dist/primitives/widgets/SliderGroup.svelte.d.ts +2 -2
- package/dist/primitives/widgets/SliderGroup.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/SliderGroup.svelte.test.js +34 -0
- package/dist/primitives/widgets/Textarea.svelte +5 -2
- package/dist/primitives/widgets/Textarea.svelte.d.ts +2 -2
- package/dist/primitives/widgets/Textarea.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/Textarea.svelte.test.js +29 -0
- package/dist/primitives/widgets/UserPicker.svelte +53 -0
- package/dist/primitives/widgets/UserPicker.svelte.d.ts +9 -0
- package/dist/primitives/widgets/UserPicker.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/UserPicker.svelte.test.js +30 -0
- package/dist/primitives/widgets/UserPicker.test.d.ts +1 -0
- package/dist/primitives/widgets/UserPicker.test.js +115 -0
- package/dist/primitives/widgets/_contract.d.ts +27 -0
- package/dist/primitives/widgets/_contract.js +10 -0
- package/dist/projects/session-state.svelte.d.ts +17 -0
- package/dist/projects/session-state.svelte.js +39 -0
- package/dist/projects/session-state.test.d.ts +1 -0
- package/dist/projects/session-state.test.js +55 -0
- package/dist/projects-shard/DeleteProjectDialog.svelte +150 -0
- package/dist/projects-shard/DeleteProjectDialog.svelte.d.ts +12 -0
- package/dist/projects-shard/DeleteProjectDialog.test.d.ts +1 -0
- package/dist/projects-shard/DeleteProjectDialog.test.js +120 -0
- package/dist/projects-shard/ProjectManage.svelte +219 -0
- package/dist/projects-shard/ProjectManage.svelte.d.ts +8 -0
- package/dist/projects-shard/ProjectsSection.svelte +120 -0
- package/dist/projects-shard/ProjectsSection.svelte.d.ts +3 -0
- package/dist/projects-shard/index.d.ts +4 -0
- package/dist/projects-shard/index.js +4 -0
- package/dist/projects-shard/projectsApi.d.ts +20 -0
- package/dist/projects-shard/projectsApi.js +44 -0
- package/dist/projects-shard/projectsApi.test.d.ts +1 -0
- package/dist/projects-shard/projectsApi.test.js +71 -0
- package/dist/projects-shard/projectsShard.svelte.d.ts +10 -0
- package/dist/projects-shard/projectsShard.svelte.js +148 -0
- package/dist/sh3core-shard/ShellHome.svelte +83 -39
- package/dist/sh3core-shard/appActions.d.ts +13 -0
- package/dist/sh3core-shard/appActions.js +181 -0
- package/dist/sh3core-shard/appActions.test.d.ts +1 -0
- package/dist/sh3core-shard/appActions.test.js +25 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.js +2 -0
- package/dist/shards/activate-scopeid.test.d.ts +1 -0
- package/dist/shards/{activate-tenantid.test.js → activate-scopeid.test.js} +6 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- /package/dist/{shards/activate-tenantid.test.d.ts → actions/resolveLabel.test.d.ts} +0 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* Confirms uninstall of an installed app launched from the home-card
|
|
4
|
+
* Uninstall action. Modeled on DeleteProjectDialog. The actual uninstall
|
|
5
|
+
* (server + local installer) is delegated to the caller via onConfirm.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
appId: string;
|
|
10
|
+
appLabel: string;
|
|
11
|
+
version: string;
|
|
12
|
+
onConfirm: () => Promise<void>;
|
|
13
|
+
close: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let { appId, appLabel, version, onConfirm, close }: Props = $props();
|
|
17
|
+
let busy = $state(false);
|
|
18
|
+
let error = $state<string | null>(null);
|
|
19
|
+
|
|
20
|
+
async function confirm() {
|
|
21
|
+
if (busy) return;
|
|
22
|
+
busy = true;
|
|
23
|
+
error = null;
|
|
24
|
+
try {
|
|
25
|
+
await onConfirm();
|
|
26
|
+
close();
|
|
27
|
+
} catch (e) {
|
|
28
|
+
error = (e as Error).message;
|
|
29
|
+
} finally {
|
|
30
|
+
busy = false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<div class="uninstall-dialog">
|
|
36
|
+
<h2>Uninstall {appLabel}?</h2>
|
|
37
|
+
<p class="hint">
|
|
38
|
+
Package id: <code>{appId}</code> · current version: <code>v{version}</code>
|
|
39
|
+
</p>
|
|
40
|
+
<p>
|
|
41
|
+
The package will be removed from this server. App data stored in your
|
|
42
|
+
browser will remain until you clear it manually.
|
|
43
|
+
</p>
|
|
44
|
+
{#if error}<p class="error">{error}</p>{/if}
|
|
45
|
+
<div class="actions">
|
|
46
|
+
<button type="button" class="danger" onclick={confirm} disabled={busy}>
|
|
47
|
+
{busy ? 'Uninstalling…' : 'Uninstall'}
|
|
48
|
+
</button>
|
|
49
|
+
<button type="button" onclick={close} disabled={busy}>Cancel</button>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<style>
|
|
54
|
+
.uninstall-dialog {
|
|
55
|
+
padding: 16px 20px;
|
|
56
|
+
max-width: 460px;
|
|
57
|
+
color: var(--shell-fg);
|
|
58
|
+
background: var(--shell-bg);
|
|
59
|
+
font: inherit;
|
|
60
|
+
}
|
|
61
|
+
h2 { margin: 0 0 8px; font-size: 16px; }
|
|
62
|
+
p { margin: 4px 0; font-size: 13px; }
|
|
63
|
+
.hint { color: var(--shell-fg-muted); font-size: 12px; }
|
|
64
|
+
.error { color: var(--shell-error, #c33); }
|
|
65
|
+
code {
|
|
66
|
+
font-family: var(--shell-font-mono, monospace);
|
|
67
|
+
background: var(--shell-bg-elevated);
|
|
68
|
+
padding: 0 4px;
|
|
69
|
+
border-radius: var(--shell-radius-sm, 3px);
|
|
70
|
+
}
|
|
71
|
+
.actions { display: flex; gap: 8px; margin-top: 16px; }
|
|
72
|
+
.actions button {
|
|
73
|
+
background: var(--shell-bg-elevated);
|
|
74
|
+
color: var(--shell-fg);
|
|
75
|
+
border: 1px solid var(--shell-border);
|
|
76
|
+
border-radius: var(--shell-radius-sm, 3px);
|
|
77
|
+
padding: 6px 14px; font: inherit; cursor: pointer;
|
|
78
|
+
}
|
|
79
|
+
.actions button.danger {
|
|
80
|
+
background: var(--shell-error, #c33);
|
|
81
|
+
color: #fff;
|
|
82
|
+
border-color: var(--shell-error, #c33);
|
|
83
|
+
}
|
|
84
|
+
.actions button:hover { border-color: var(--shell-accent); }
|
|
85
|
+
.actions button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
86
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
appId: string;
|
|
3
|
+
appLabel: string;
|
|
4
|
+
version: string;
|
|
5
|
+
onConfirm: () => Promise<void>;
|
|
6
|
+
close: () => void;
|
|
7
|
+
}
|
|
8
|
+
declare const UninstallAppDialog: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type UninstallAppDialog = ReturnType<typeof UninstallAppDialog>;
|
|
10
|
+
export default UninstallAppDialog;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Shared permission-diff confirmation flow for the `update` path. Opens
|
|
3
|
+
* PermissionConfirmModal via modalManager and resolves to the user's choice.
|
|
4
|
+
* Used by InstalledView's Update button and the home-card "Check for
|
|
5
|
+
* updates" context-menu action.
|
|
6
|
+
*/
|
|
7
|
+
import { modalManager } from '../../overlays/modal';
|
|
8
|
+
import PermissionConfirmModal from './PermissionConfirmModal.svelte';
|
|
9
|
+
export function openPermissionConfirmModal(pkg, toVersion, added, removed) {
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
const props = {
|
|
12
|
+
mode: 'update',
|
|
13
|
+
pkg: { label: pkg.label, version: toVersion },
|
|
14
|
+
fromVersion: pkg.version,
|
|
15
|
+
added,
|
|
16
|
+
removed,
|
|
17
|
+
onConfirm: () => {
|
|
18
|
+
handle.close();
|
|
19
|
+
resolve(true);
|
|
20
|
+
},
|
|
21
|
+
onCancel: () => {
|
|
22
|
+
handle.close();
|
|
23
|
+
resolve(false);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
const handle = modalManager.open(PermissionConfirmModal, props);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -3,6 +3,12 @@ import type { StateZones } from '../../state/zones.svelte';
|
|
|
3
3
|
import type { ResolvedPackage } from '../../registry/client';
|
|
4
4
|
import type { InstalledPackage } from '../../registry/types';
|
|
5
5
|
import type { EnvState } from '../../env/types';
|
|
6
|
+
/**
|
|
7
|
+
* Pick a version entry from a resolved package. When `requested` is
|
|
8
|
+
* undefined returns `latest`; when set, finds the matching entry by
|
|
9
|
+
* exact version string. Throws if the requested version is absent.
|
|
10
|
+
*/
|
|
11
|
+
export declare function pickVersion(pkg: ResolvedPackage, requested: string | undefined): typeof pkg.latest;
|
|
6
12
|
/**
|
|
7
13
|
* Compute added and removed permissions between two manifest snapshots.
|
|
8
14
|
* Order within each array follows the input order of `newPerms` (for added)
|
|
@@ -34,7 +40,8 @@ export interface StoreContext {
|
|
|
34
40
|
isAdmin: boolean;
|
|
35
41
|
refreshCatalog(): Promise<void>;
|
|
36
42
|
refreshInstalled(): Promise<void>;
|
|
37
|
-
updatePackage(id: string, confirmPermissionChange?: (added: string[], removed: string[]) => Promise<boolean
|
|
43
|
+
updatePackage(id: string, confirmPermissionChange?: (added: string[], removed: string[]) => Promise<boolean>, version?: string): Promise<void>;
|
|
44
|
+
uninstallPackage(id: string): Promise<void>;
|
|
38
45
|
addRegistry(url: string): Promise<void>;
|
|
39
46
|
removeRegistry(url: string): Promise<void>;
|
|
40
47
|
}
|
|
@@ -16,12 +16,13 @@ import StoreView from './StoreView.svelte';
|
|
|
16
16
|
import InstalledView from './InstalledView.svelte';
|
|
17
17
|
import { fetchRegistries, fetchBundle, fetchServerBundle, buildPackageMeta } from '../../registry/client';
|
|
18
18
|
import { installPackage, listInstalledPackages } from '../../registry/installer';
|
|
19
|
+
import { uninstallPackage as installerUninstallPackage } from '../../registry/installer';
|
|
19
20
|
import { loadBundle, loadMeta, savePackage } from '../../registry/storage';
|
|
20
21
|
import { loadBundleModule } from '../../registry/loader';
|
|
21
22
|
import { extractBundlePermissions } from '../../registry/permission-descriptions';
|
|
22
|
-
import { serverInstallPackage, fetchServerPackages } from '../../env/client';
|
|
23
|
+
import { serverInstallPackage, fetchServerPackages, serverUninstallPackage } from '../../env/client';
|
|
23
24
|
import { VERSION } from '../../version';
|
|
24
|
-
import { installVerb, uninstallVerb, appinfoVerb } from './verbs';
|
|
25
|
+
import { installVerb, uninstallVerb, appinfoVerb, updateVerb } from './verbs';
|
|
25
26
|
/**
|
|
26
27
|
* Compare two semver-like version strings.
|
|
27
28
|
* Returns true only if `available` is strictly greater than `installed`.
|
|
@@ -43,6 +44,20 @@ function isNewerVersion(available, installed) {
|
|
|
43
44
|
}
|
|
44
45
|
return false;
|
|
45
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Pick a version entry from a resolved package. When `requested` is
|
|
49
|
+
* undefined returns `latest`; when set, finds the matching entry by
|
|
50
|
+
* exact version string. Throws if the requested version is absent.
|
|
51
|
+
*/
|
|
52
|
+
export function pickVersion(pkg, requested) {
|
|
53
|
+
if (requested === undefined)
|
|
54
|
+
return pkg.latest;
|
|
55
|
+
const found = pkg.entry.versions.find((v) => v.version === requested);
|
|
56
|
+
if (!found) {
|
|
57
|
+
throw new Error(`version ${requested} not in catalog for ${pkg.entry.id}`);
|
|
58
|
+
}
|
|
59
|
+
return found;
|
|
60
|
+
}
|
|
46
61
|
/**
|
|
47
62
|
* Compute added and removed permissions between two manifest snapshots.
|
|
48
63
|
* Order within each array follows the input order of `newPerms` (for added)
|
|
@@ -149,21 +164,32 @@ export const storeShard = {
|
|
|
149
164
|
const registries = env.registries.filter((r) => r !== url);
|
|
150
165
|
await ctx.envUpdate({ registries });
|
|
151
166
|
}
|
|
152
|
-
async function updatePackage(id, confirmPermissionChange) {
|
|
167
|
+
async function updatePackage(id, confirmPermissionChange, version) {
|
|
153
168
|
var _a, _b, _c, _d;
|
|
154
|
-
|
|
169
|
+
// Source the catalog entry. Without an explicit version we use the
|
|
170
|
+
// updatable map (which encodes the "newer than installed" check); with a
|
|
171
|
+
// version we look up the package in the full catalog so downgrades and
|
|
172
|
+
// same-version reinstalls work.
|
|
173
|
+
let catalogEntry;
|
|
174
|
+
if (version === undefined) {
|
|
175
|
+
catalogEntry = state.ephemeral.updatable[id];
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
catalogEntry = state.ephemeral.catalog.find((p) => p.entry.id === id);
|
|
179
|
+
}
|
|
155
180
|
if (!catalogEntry)
|
|
156
181
|
return;
|
|
157
182
|
const installedRecord = state.ephemeral.installed.find((p) => p.id === id);
|
|
158
183
|
if (!installedRecord)
|
|
159
184
|
return;
|
|
185
|
+
const picked = pickVersion(catalogEntry, version);
|
|
160
186
|
// 1. Fetch new bundle(s).
|
|
161
|
-
const bundle = await fetchBundle(
|
|
187
|
+
const bundle = await fetchBundle(picked, catalogEntry.sourceRegistry);
|
|
162
188
|
let serverBundle;
|
|
163
|
-
if (
|
|
164
|
-
serverBundle = await fetchServerBundle(
|
|
189
|
+
if (picked.serverBundleUrl) {
|
|
190
|
+
serverBundle = await fetchServerBundle(picked, catalogEntry.sourceRegistry);
|
|
165
191
|
}
|
|
166
|
-
const meta = buildPackageMeta(catalogEntry,
|
|
192
|
+
const meta = buildPackageMeta(catalogEntry, picked);
|
|
167
193
|
// 2. Load the module once for permission extraction and install reuse.
|
|
168
194
|
const loaded = await loadBundleModule(bundle);
|
|
169
195
|
const newPerms = extractBundlePermissions(loaded);
|
|
@@ -203,7 +229,7 @@ export const storeShard = {
|
|
|
203
229
|
contractVersion: meta.contractVersion,
|
|
204
230
|
sourceRegistry: meta.sourceRegistry,
|
|
205
231
|
installedAt: new Date().toISOString(),
|
|
206
|
-
requiredShards: (_b = (_a =
|
|
232
|
+
requiredShards: (_b = (_a = picked.requires) === null || _a === void 0 ? void 0 : _a.map((r) => r.id)) !== null && _b !== void 0 ? _b : [],
|
|
207
233
|
};
|
|
208
234
|
const serverResult = await serverInstallPackage(manifest, bundle, serverBundle);
|
|
209
235
|
if (!serverResult.ok) {
|
|
@@ -231,6 +257,11 @@ export const storeShard = {
|
|
|
231
257
|
}
|
|
232
258
|
await refreshInstalled();
|
|
233
259
|
}
|
|
260
|
+
async function uninstallPackage(id) {
|
|
261
|
+
await serverUninstallPackage(id);
|
|
262
|
+
await installerUninstallPackage(id);
|
|
263
|
+
await refreshInstalled();
|
|
264
|
+
}
|
|
234
265
|
storeContext = {
|
|
235
266
|
env,
|
|
236
267
|
state,
|
|
@@ -238,6 +269,7 @@ export const storeShard = {
|
|
|
238
269
|
refreshCatalog,
|
|
239
270
|
refreshInstalled,
|
|
240
271
|
updatePackage,
|
|
272
|
+
uninstallPackage,
|
|
241
273
|
addRegistry,
|
|
242
274
|
removeRegistry,
|
|
243
275
|
};
|
|
@@ -268,6 +300,7 @@ export const storeShard = {
|
|
|
268
300
|
ctx.registerVerb(installVerb);
|
|
269
301
|
ctx.registerVerb(uninstallVerb);
|
|
270
302
|
ctx.registerVerb(appinfoVerb);
|
|
303
|
+
ctx.registerVerb(updateVerb);
|
|
271
304
|
// refreshInstalled can run immediately (hits server, no env needed).
|
|
272
305
|
refreshInstalled();
|
|
273
306
|
},
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* updatePackage() — version-aware unit tests. We exercise the version
|
|
3
|
+
* resolution branch only; the full flow (server install + permission diff)
|
|
4
|
+
* is covered indirectly through InstalledView component tests.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { pickVersion } from './storeShard.svelte';
|
|
8
|
+
function mkPkg(id, versions) {
|
|
9
|
+
return {
|
|
10
|
+
sourceRegistry: 'https://example.test/registry.json',
|
|
11
|
+
entry: {
|
|
12
|
+
id,
|
|
13
|
+
label: id,
|
|
14
|
+
author: { name: 'tester' },
|
|
15
|
+
description: '',
|
|
16
|
+
versions: versions.map((v) => ({ version: v, bundleUrl: `${id}-${v}.js`, integrity: 'sha256-x' })),
|
|
17
|
+
},
|
|
18
|
+
latest: { version: versions[0], bundleUrl: `${id}-${versions[0]}.js`, integrity: 'sha256-x' },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
describe('pickVersion', () => {
|
|
22
|
+
it('returns latest when no version requested', () => {
|
|
23
|
+
const pkg = mkPkg('foo', ['2.0.0', '1.0.0']);
|
|
24
|
+
expect(pickVersion(pkg, undefined).version).toBe('2.0.0');
|
|
25
|
+
});
|
|
26
|
+
it('returns the matching entry when version exists', () => {
|
|
27
|
+
const pkg = mkPkg('foo', ['2.0.0', '1.0.0']);
|
|
28
|
+
expect(pickVersion(pkg, '1.0.0').version).toBe('1.0.0');
|
|
29
|
+
});
|
|
30
|
+
it('throws when version is not in catalog', () => {
|
|
31
|
+
const pkg = mkPkg('foo', ['2.0.0']);
|
|
32
|
+
expect(() => pickVersion(pkg, '9.9.9')).toThrow(/version 9\.9\.9 not in catalog for foo/);
|
|
33
|
+
});
|
|
34
|
+
});
|
package/dist/app/store/verbs.js
CHANGED
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
import { storeContext } from './storeShard.svelte';
|
|
8
8
|
import { fetchBundle, fetchServerBundle, buildPackageMeta } from '../../registry/client';
|
|
9
9
|
import { installPackage } from '../../registry/installer';
|
|
10
|
-
import { serverInstallPackage
|
|
11
|
-
import { uninstallPackage } from '../../registry/installer';
|
|
10
|
+
import { serverInstallPackage } from '../../env/client';
|
|
12
11
|
function findInCatalog(id) {
|
|
13
12
|
return storeContext.state.ephemeral.catalog.find((p) => p.entry.id === id);
|
|
14
13
|
}
|
|
@@ -146,9 +145,7 @@ export const uninstallVerb = {
|
|
|
146
145
|
ts: Date.now(),
|
|
147
146
|
});
|
|
148
147
|
try {
|
|
149
|
-
await
|
|
150
|
-
await uninstallPackage(id);
|
|
151
|
-
await storeContext.refreshInstalled();
|
|
148
|
+
await storeContext.uninstallPackage(id);
|
|
152
149
|
ctx.scrollback.push({
|
|
153
150
|
kind: 'status',
|
|
154
151
|
text: `uninstalled ${id}`,
|
|
@@ -166,6 +163,83 @@ export const uninstallVerb = {
|
|
|
166
163
|
}
|
|
167
164
|
},
|
|
168
165
|
};
|
|
166
|
+
export const updateVerb = {
|
|
167
|
+
name: 'update',
|
|
168
|
+
summary: 'Update an installed package (sh3-store:update <id> [version]). When ' +
|
|
169
|
+
'version is omitted, bumps to latest; with a version, installs that ' +
|
|
170
|
+
'exact version (downgrade or same-version reinstall allowed).',
|
|
171
|
+
async run(ctx, args) {
|
|
172
|
+
var _a, _b;
|
|
173
|
+
const id = args[0];
|
|
174
|
+
const version = args[1];
|
|
175
|
+
if (!id) {
|
|
176
|
+
ctx.scrollback.push({
|
|
177
|
+
kind: 'status',
|
|
178
|
+
text: 'usage: sh3-store:update <package-id> [version]',
|
|
179
|
+
level: 'warn',
|
|
180
|
+
ts: Date.now(),
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const installed = findInstalled(id);
|
|
185
|
+
if (!installed) {
|
|
186
|
+
ctx.scrollback.push({
|
|
187
|
+
kind: 'status',
|
|
188
|
+
text: `package "${id}" is not installed`,
|
|
189
|
+
level: 'error',
|
|
190
|
+
ts: Date.now(),
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const fromVersion = installed.version;
|
|
195
|
+
const action = version && version === fromVersion
|
|
196
|
+
? 'reinstalling'
|
|
197
|
+
: version
|
|
198
|
+
? `updating ${id} from v${fromVersion} to v${version}`
|
|
199
|
+
: `updating ${id} from v${fromVersion} to latest`;
|
|
200
|
+
ctx.scrollback.push({
|
|
201
|
+
kind: 'status',
|
|
202
|
+
text: action.startsWith('reinstalling')
|
|
203
|
+
? `reinstalling ${id} v${version}…`
|
|
204
|
+
: `${action}…`,
|
|
205
|
+
level: 'info',
|
|
206
|
+
ts: Date.now(),
|
|
207
|
+
});
|
|
208
|
+
// Verb path can't surface a modal. If the new bundle changes permissions,
|
|
209
|
+
// auto-reject and tell the user to use the UI flow.
|
|
210
|
+
const confirmReject = async (added, removed) => {
|
|
211
|
+
ctx.scrollback.push({
|
|
212
|
+
kind: 'status',
|
|
213
|
+
text: `permission change required (added: ${added.join(', ') || 'none'}; ` +
|
|
214
|
+
`removed: ${removed.join(', ') || 'none'}) — open the store and use ` +
|
|
215
|
+
`the Update button to review and apply.`,
|
|
216
|
+
level: 'warn',
|
|
217
|
+
ts: Date.now(),
|
|
218
|
+
});
|
|
219
|
+
return false;
|
|
220
|
+
};
|
|
221
|
+
try {
|
|
222
|
+
await storeContext.updatePackage(id, confirmReject, version);
|
|
223
|
+
const finalVersion = (_b = version !== null && version !== void 0 ? version : (_a = findInstalled(id)) === null || _a === void 0 ? void 0 : _a.version) !== null && _b !== void 0 ? _b : '?';
|
|
224
|
+
ctx.scrollback.push({
|
|
225
|
+
kind: 'status',
|
|
226
|
+
text: version && version === fromVersion
|
|
227
|
+
? `reinstalled ${id} v${finalVersion}`
|
|
228
|
+
: `updated ${id} v${finalVersion}`,
|
|
229
|
+
level: 'info',
|
|
230
|
+
ts: Date.now(),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
ctx.scrollback.push({
|
|
235
|
+
kind: 'status',
|
|
236
|
+
text: `update failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
237
|
+
level: 'error',
|
|
238
|
+
ts: Date.now(),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
};
|
|
169
243
|
export const appinfoVerb = {
|
|
170
244
|
name: 'appinfo',
|
|
171
245
|
summary: 'Show info about a package (installed status, version, catalog details).',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { updateVerb } from './verbs';
|
|
3
|
+
import * as shard from './storeShard.svelte';
|
|
4
|
+
function mkCtx() {
|
|
5
|
+
const lines = [];
|
|
6
|
+
return {
|
|
7
|
+
ctx: {
|
|
8
|
+
scrollback: { push: (e) => lines.push(e) },
|
|
9
|
+
},
|
|
10
|
+
lines,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
describe('updateVerb', () => {
|
|
14
|
+
it('warns when no id is given', async () => {
|
|
15
|
+
var _a;
|
|
16
|
+
const { ctx, lines } = mkCtx();
|
|
17
|
+
await updateVerb.run(ctx, []);
|
|
18
|
+
expect((_a = lines.at(-1)) === null || _a === void 0 ? void 0 : _a.text).toMatch(/usage: sh3-store:update/);
|
|
19
|
+
});
|
|
20
|
+
it('delegates to storeContext.updatePackage when version omitted', async () => {
|
|
21
|
+
const fake = vi.fn().mockResolvedValue(undefined);
|
|
22
|
+
// @ts-expect-error — module-level singleton is not readonly at runtime
|
|
23
|
+
shard.storeContext = {
|
|
24
|
+
state: { ephemeral: { installed: [{ id: 'foo', version: '1.0.0' }] } },
|
|
25
|
+
updatePackage: fake,
|
|
26
|
+
};
|
|
27
|
+
const { ctx, lines } = mkCtx();
|
|
28
|
+
await updateVerb.run(ctx, ['foo']);
|
|
29
|
+
expect(fake).toHaveBeenCalledWith('foo', expect.any(Function), undefined);
|
|
30
|
+
expect(lines.some((l) => /updated foo/.test(l.text))).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
it('passes version through when provided', async () => {
|
|
33
|
+
const fake = vi.fn().mockResolvedValue(undefined);
|
|
34
|
+
// @ts-expect-error
|
|
35
|
+
shard.storeContext = {
|
|
36
|
+
state: { ephemeral: { installed: [{ id: 'foo', version: '2.0.0' }] } },
|
|
37
|
+
updatePackage: fake,
|
|
38
|
+
};
|
|
39
|
+
const { ctx } = mkCtx();
|
|
40
|
+
await updateVerb.run(ctx, ['foo', '1.0.0']);
|
|
41
|
+
expect(fake).toHaveBeenCalledWith('foo', expect.any(Function), '1.0.0');
|
|
42
|
+
});
|
|
43
|
+
it('reports failure as error scrollback line', async () => {
|
|
44
|
+
const fake = vi.fn().mockRejectedValue(new Error('boom'));
|
|
45
|
+
// @ts-expect-error
|
|
46
|
+
shard.storeContext = {
|
|
47
|
+
state: { ephemeral: { installed: [{ id: 'foo', version: '1.0.0' }] } },
|
|
48
|
+
updatePackage: fake,
|
|
49
|
+
};
|
|
50
|
+
const { ctx, lines } = mkCtx();
|
|
51
|
+
await updateVerb.run(ctx, ['foo']);
|
|
52
|
+
const last = lines.at(-1);
|
|
53
|
+
expect(last.level).toBe('error');
|
|
54
|
+
expect(last.text).toMatch(/update failed: boom/);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* Customize — pick an icon and color override for a single app.
|
|
4
|
+
* Reads the existing override on mount via untrack (Svelte 5 idiom for
|
|
5
|
+
* one-shot prop reads), so editing the form doesn't subscribe to the
|
|
6
|
+
* shard's reactive state. Save/Reset/Cancel are mutually exclusive.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { untrack } from 'svelte';
|
|
10
|
+
import IconPicker from '../primitives/widgets/IconPicker.svelte';
|
|
11
|
+
import ColorSwatch from '../primitives/widgets/ColorSwatch.svelte';
|
|
12
|
+
import iconsUrl from '../assets/icons.svg';
|
|
13
|
+
import { listRegisteredApps } from '../api';
|
|
14
|
+
import { getAppearance, setAppearance } from './appearanceState.svelte';
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
appId: string;
|
|
18
|
+
appLabel: string;
|
|
19
|
+
close: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let { appId, appLabel, close }: Props = $props();
|
|
23
|
+
|
|
24
|
+
const initial = untrack(() => getAppearance(appId));
|
|
25
|
+
const manifestIcon = untrack(
|
|
26
|
+
() => listRegisteredApps().find((m) => m.id === appId)?.icon,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
let icon = $state<string | undefined>(initial?.icon);
|
|
30
|
+
let color = $state<string | undefined>(initial?.color);
|
|
31
|
+
let label = $state<string>(initial?.label ?? '');
|
|
32
|
+
let pickerOpen = $state<boolean>(initial?.icon !== undefined);
|
|
33
|
+
|
|
34
|
+
const hasOverride = $derived(initial !== undefined);
|
|
35
|
+
const effectiveLabel = $derived(label.trim() === '' ? appLabel : label.trim());
|
|
36
|
+
const effectiveIcon = $derived(icon ?? manifestIcon ?? 'box');
|
|
37
|
+
|
|
38
|
+
function save() {
|
|
39
|
+
const trimmed = label.trim();
|
|
40
|
+
setAppearance(appId, {
|
|
41
|
+
icon,
|
|
42
|
+
color,
|
|
43
|
+
label: trimmed === '' ? undefined : trimmed,
|
|
44
|
+
});
|
|
45
|
+
close();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function reset() {
|
|
49
|
+
setAppearance(appId, undefined);
|
|
50
|
+
close();
|
|
51
|
+
}
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<div class="app-appearance">
|
|
55
|
+
<h2>Customize {appLabel}</h2>
|
|
56
|
+
|
|
57
|
+
<div class="preview">
|
|
58
|
+
<div
|
|
59
|
+
class="preview-card"
|
|
60
|
+
class:preview-card--tinted={color !== undefined}
|
|
61
|
+
style:--card-color={color ?? 'transparent'}
|
|
62
|
+
>
|
|
63
|
+
<span class="preview-card-square">
|
|
64
|
+
<svg class="preview-card-icon"><use href="{iconsUrl}#{effectiveIcon}" /></svg>
|
|
65
|
+
</span>
|
|
66
|
+
<span class="preview-card-label">{effectiveLabel}</span>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<label class="row"><span>Name <em>(empty = default)</em></span>
|
|
71
|
+
<input
|
|
72
|
+
type="text"
|
|
73
|
+
bind:value={label}
|
|
74
|
+
placeholder={appLabel}
|
|
75
|
+
class="name-input"
|
|
76
|
+
/>
|
|
77
|
+
</label>
|
|
78
|
+
|
|
79
|
+
<div class="row">
|
|
80
|
+
<span>Icon</span>
|
|
81
|
+
{#if !pickerOpen}
|
|
82
|
+
<button type="button" class="link" onclick={() => (pickerOpen = true)}>
|
|
83
|
+
Change icon
|
|
84
|
+
</button>
|
|
85
|
+
{:else}
|
|
86
|
+
<IconPicker bind:value={icon} />
|
|
87
|
+
<button type="button" class="link link--align-right" onclick={() => (pickerOpen = false)}>
|
|
88
|
+
Hide picker
|
|
89
|
+
</button>
|
|
90
|
+
{/if}
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<label class="row"><span>Color</span>
|
|
94
|
+
<ColorSwatch value={color ?? '#888888'} onchange={(v) => (color = v)} />
|
|
95
|
+
</label>
|
|
96
|
+
|
|
97
|
+
<div class="actions">
|
|
98
|
+
<button type="button" class="primary" onclick={save}>Save</button>
|
|
99
|
+
<button type="button" onclick={reset} disabled={!hasOverride}>Reset</button>
|
|
100
|
+
<button type="button" onclick={close}>Cancel</button>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<style>
|
|
105
|
+
.app-appearance {
|
|
106
|
+
padding: 16px 20px;
|
|
107
|
+
max-width: 460px;
|
|
108
|
+
color: var(--shell-fg);
|
|
109
|
+
background: var(--shell-bg);
|
|
110
|
+
font: inherit;
|
|
111
|
+
}
|
|
112
|
+
h2 { margin: 0 0 12px; font-size: 16px; }
|
|
113
|
+
.row { display: flex; flex-direction: column; gap: 4px; margin-bottom: 12px; font-size: 13px; }
|
|
114
|
+
.row span { color: var(--shell-fg-muted); }
|
|
115
|
+
.row span em { font-style: italic; opacity: 0.7; }
|
|
116
|
+
.name-input {
|
|
117
|
+
background: var(--shell-bg-elevated);
|
|
118
|
+
color: var(--shell-fg);
|
|
119
|
+
border: 1px solid var(--shell-border);
|
|
120
|
+
border-radius: var(--shell-radius-sm, 3px);
|
|
121
|
+
padding: 6px 8px; font: inherit; font-size: 13px;
|
|
122
|
+
}
|
|
123
|
+
.link {
|
|
124
|
+
align-self: flex-start;
|
|
125
|
+
background: transparent;
|
|
126
|
+
border: none;
|
|
127
|
+
padding: 0;
|
|
128
|
+
color: var(--shell-accent);
|
|
129
|
+
font: inherit;
|
|
130
|
+
font-size: 13px;
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
text-decoration: underline;
|
|
133
|
+
}
|
|
134
|
+
.link--align-right { align-self: flex-end; margin-top: 4px; }
|
|
135
|
+
.link:hover { color: var(--shell-fg); }
|
|
136
|
+
.preview { display: flex; justify-content: center; margin-bottom: 16px; }
|
|
137
|
+
.preview-card {
|
|
138
|
+
display: flex; flex-direction: column;
|
|
139
|
+
align-items: center; gap: 6px;
|
|
140
|
+
max-width: 160px;
|
|
141
|
+
}
|
|
142
|
+
.preview-card-square {
|
|
143
|
+
width: 64px; height: 64px;
|
|
144
|
+
display: flex; align-items: center; justify-content: center;
|
|
145
|
+
background: var(--shell-grad-bg-elevated, var(--shell-bg-elevated));
|
|
146
|
+
border: 1px solid var(--shell-border);
|
|
147
|
+
border-radius: var(--shell-radius-md);
|
|
148
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25), 0 1px 2px rgba(0, 0, 0, 0.15);
|
|
149
|
+
}
|
|
150
|
+
.preview-card--tinted .preview-card-square { background: var(--card-color); }
|
|
151
|
+
.preview-card-icon { width: 28px; height: 28px; color: var(--shell-fg); }
|
|
152
|
+
.preview-card-label {
|
|
153
|
+
font-weight: 600; font-size: 11px; line-height: 1.2;
|
|
154
|
+
text-align: center; word-break: break-word;
|
|
155
|
+
overflow: hidden;
|
|
156
|
+
display: -webkit-box;
|
|
157
|
+
-webkit-box-orient: vertical;
|
|
158
|
+
-webkit-line-clamp: 2;
|
|
159
|
+
line-clamp: 2;
|
|
160
|
+
}
|
|
161
|
+
.preview-card-icon { width: 24px; height: 24px; color: var(--shell-fg); }
|
|
162
|
+
.preview-card-label { font-weight: 600; padding: 0 4px; line-height: 1.2; }
|
|
163
|
+
.actions { display: flex; gap: 8px; margin-top: 16px; }
|
|
164
|
+
.actions button {
|
|
165
|
+
background: var(--shell-bg-elevated);
|
|
166
|
+
color: var(--shell-fg);
|
|
167
|
+
border: 1px solid var(--shell-border);
|
|
168
|
+
border-radius: var(--shell-radius-sm, 3px);
|
|
169
|
+
padding: 6px 14px; font: inherit; cursor: pointer;
|
|
170
|
+
}
|
|
171
|
+
.actions button.primary { background: var(--shell-accent); color: #fff; border-color: var(--shell-accent); }
|
|
172
|
+
.actions button:hover { border-color: var(--shell-accent); }
|
|
173
|
+
.actions button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
174
|
+
</style>
|