@svadmin/create 0.41.0 → 0.41.1

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.
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import type { ProductPagePlan } from '@svadmin/core/product-pages';
2
3
  import { productPagePlans, productSurfaceRoutes } from './generated/pages';
3
4
 
4
5
  interface Props {
@@ -6,8 +7,24 @@
6
7
  params: Record<string, string | undefined>;
7
8
  }
8
9
 
10
+ type SurfaceState = ProductPagePlan['states'][number];
11
+ const stateByScenario: Record<string, SurfaceState | undefined> = {
12
+ empty: 'empty',
13
+ loading: 'loading',
14
+ error: 'error',
15
+ denied: 'forbidden',
16
+ };
17
+
9
18
  let { route }: Props = $props();
10
19
  const page = $derived(productPagePlans.find(candidate => productSurfaceRoutes[route] === candidate.id));
20
+ const requestedScenario = typeof location !== 'undefined'
21
+ ? new URLSearchParams(location.search).get('scenario') ?? 'normal'
22
+ : 'normal';
23
+ const scenarioState = $derived.by(() => {
24
+ const candidate = page;
25
+ const state = stateByScenario[requestedScenario];
26
+ return candidate && state !== undefined && candidate.states.includes(state) ? state : undefined;
27
+ });
11
28
  </script>
12
29
 
13
30
  <svelte:head>
@@ -33,31 +50,53 @@
33
50
  {/each}
34
51
  </div>
35
52
 
36
- <div class="surface-grid">
37
- {#each page.sections as section (section.id)}
38
- <section class="surface-card">
39
- <div class="card-heading">
40
- <h2>{section.title}</h2>
41
- <span>区块</span>
42
- </div>
43
- <p>此区块服务于当前工作流,并展示产品计划声明的资源。</p>
44
- {#if section.resourceIds.length}
45
- <div class="resource-list">
46
- {#each section.resourceIds as resourceId (resourceId)}
47
- <span>{resourceId}</span>
48
- {/each}
53
+ {#if scenarioState}
54
+ <section class="surface-state" data-state={scenarioState}>
55
+ {#if scenarioState === 'loading'}
56
+ <span class="state-icon" aria-hidden="true">◌</span>
57
+ <h2>正在加载工作台</h2>
58
+ <p>正在准备当前工作流需要的数据。</p>
59
+ {:else if scenarioState === 'empty'}
60
+ <span class="state-icon" aria-hidden="true">○</span>
61
+ <h2>当前没有待处理数据</h2>
62
+ <p>工作流已经准备好;当业务数据进入后,这里会显示下一步操作。</p>
63
+ {:else if scenarioState === 'error'}
64
+ <span class="state-icon" aria-hidden="true">!</span>
65
+ <h2>暂时无法加载工作台</h2>
66
+ <p>数据 provider 返回了错误,请稍后重试或联系管理员。</p>
67
+ {:else if scenarioState === 'forbidden'}
68
+ <span class="state-icon" aria-hidden="true">×</span>
69
+ <h2>没有访问权限</h2>
70
+ <p>当前角色没有访问这个工作流的权限。</p>
71
+ {/if}
72
+ </section>
73
+ {:else}
74
+ <div class="surface-grid">
75
+ {#each page.sections as section (section.id)}
76
+ <section class="surface-card">
77
+ <div class="card-heading">
78
+ <h2>{section.title}</h2>
79
+ <span>区块</span>
49
80
  </div>
50
- {:else}
51
- <p class="muted">此区块暂无数据资源,等待接入真实 provider。</p>
52
- {/if}
53
- </section>
54
- {/each}
55
- </div>
81
+ <p>此区块服务于当前工作流,并展示产品计划声明的资源。</p>
82
+ {#if section.resourceIds.length}
83
+ <div class="resource-list">
84
+ {#each section.resourceIds as resourceId (resourceId)}
85
+ <span>{resourceId}</span>
86
+ {/each}
87
+ </div>
88
+ {:else}
89
+ <p class="muted">此区块暂无数据资源,等待接入真实 provider。</p>
90
+ {/if}
91
+ </section>
92
+ {/each}
93
+ </div>
56
94
 
57
- <aside class="contract-note">
58
- <strong>动作仍是产品元数据</strong>
59
- <span>接入 defineCommand 或资源契约后,再启用真实提交、审批和写入。</span>
60
- </aside>
95
+ <aside class="contract-note">
96
+ <strong>动作仍是产品元数据</strong>
97
+ <span>接入 defineCommand 或资源契约后,再启用真实提交、审批和写入。</span>
98
+ </aside>
99
+ {/if}
61
100
  {:else}
62
101
  <section class="surface-card">
63
102
  <h1>未找到页面计划</h1>
@@ -78,6 +117,10 @@
78
117
  .state-strip { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; }
79
118
  .state { background: #eef0f3; color: #596273; }
80
119
  .surface-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; }
120
+ .surface-state { display: grid; justify-items: center; gap: 8px; min-height: 220px; padding: 42px 24px; border: 1px dashed #cdd5e4; border-radius: 18px; background: #f8faff; text-align: center; }
121
+ .surface-state h2, .surface-state p { margin: 0; }
122
+ .surface-state p { max-width: 520px; color: #65708a; line-height: 1.6; }
123
+ .state-icon { display: grid; place-items: center; width: 36px; height: 36px; border-radius: 50%; background: #e8eefc; color: #3558a8; font-size: 22px; font-weight: 700; }
81
124
  .surface-card { min-height: 150px; padding: 22px; border: 1px solid #e4e7ed; border-radius: 18px; background: #fff; box-shadow: 0 10px 30px rgb(32 45 72 / 5%); }
82
125
  .card-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
83
126
  .card-heading span { color: #8a94a7; font-size: 12px; }
package/dist/index.js CHANGED
@@ -18923,7 +18923,11 @@ function buildProductPagePlan(surface, plan) {
18923
18923
  const actionById = new Map(plan.actions.map((action) => [action.id, action]));
18924
18924
  const primaryAction = surface.primaryActionId === undefined ? undefined : actionById.get(surface.primaryActionId);
18925
18925
  const secondaryActions = surface.actionIds.filter((id) => id !== surface.primaryActionId).map((id) => actionById.get(id)).filter((action) => action !== undefined);
18926
- const sections = surface.sections.length ? surface.sections.map((title, index) => ({ id: `${surface.id}.section.${index + 1}`, title, resourceIds: surface.resourceIds })) : [{ id: `${surface.id}.content`, title: surface.name, resourceIds: surface.resourceIds }];
18926
+ const sections = surface.sections.length ? surface.sections.map((title, index) => ({
18927
+ id: `${surface.id}.section.${index + 1}`,
18928
+ title,
18929
+ resourceIds: index === 0 ? surface.resourceIds : []
18930
+ })) : [{ id: `${surface.id}.content`, title: surface.name, resourceIds: surface.resourceIds }];
18927
18931
  return {
18928
18932
  id: surface.id,
18929
18933
  kind: surface.kind,
@@ -18952,6 +18956,38 @@ function reviewProductPlan(plan) {
18952
18956
  const resourceIds = new Set(plan.resources.map((resource) => resource.id));
18953
18957
  const surfaceIds = new Set(plan.surfaces.map((surface) => surface.id));
18954
18958
  const actionIds = new Set(plan.actions.map((action) => action.id));
18959
+ const runtimeNames = new Map;
18960
+ for (const resource of plan.resources) {
18961
+ const runtimeName = resource.runtimeName ?? resource.name;
18962
+ const previous = runtimeNames.get(runtimeName);
18963
+ if (previous !== undefined) {
18964
+ addIssue(issues, {
18965
+ code: "resource.duplicate-runtime-name",
18966
+ severity: "error",
18967
+ message: `Resources "${resource.label}" and "${previous}" resolve to the same runtime name "${runtimeName}".`,
18968
+ path: `resources.${resource.id}.runtimeName`,
18969
+ suggestion: "Give every generated resource route a unique runtime name."
18970
+ });
18971
+ } else {
18972
+ runtimeNames.set(runtimeName, resource.label);
18973
+ }
18974
+ }
18975
+ const surfaceRoutes = new Map;
18976
+ for (const surface of plan.surfaces) {
18977
+ const route = surface.route ?? `/workflows/${surface.id}`;
18978
+ const previous = surfaceRoutes.get(route);
18979
+ if (previous !== undefined) {
18980
+ addIssue(issues, {
18981
+ code: "surface.duplicate-route",
18982
+ severity: "error",
18983
+ message: `Surfaces "${surface.name}" and "${previous}" resolve to the same route "${route}".`,
18984
+ path: `surfaces.${surface.id}.route`,
18985
+ suggestion: "Give every generated surface a unique route."
18986
+ });
18987
+ } else {
18988
+ surfaceRoutes.set(route, surface.name);
18989
+ }
18990
+ }
18955
18991
  for (const workflow of plan.workflows) {
18956
18992
  if (!workflow.actorRoleIds.length)
18957
18993
  addIssue(issues, { code: "workflow.missing-actor", severity: "error", message: `Workflow "${workflow.name}" has no actor role.`, path: `workflows.${workflow.id}.actorRoleIds` });
@@ -18967,10 +19003,42 @@ function reviewProductPlan(plan) {
18967
19003
  for (const workflowId of resource.workflowIds)
18968
19004
  if (!has2(workflowIds, workflowId))
18969
19005
  addIssue(issues, { code: "resource.unknown-workflow", severity: "error", message: `Resource "${resource.label}" references unknown workflow "${workflowId}".`, path: `resources.${resource.id}.workflowIds` });
19006
+ for (const roleId of resource.roleIds)
19007
+ if (!has2(roleIds, roleId))
19008
+ addIssue(issues, { code: "resource.unknown-role", severity: "error", message: `Resource "${resource.label}" references unknown role "${roleId}".`, path: `resources.${resource.id}.roleIds` });
19009
+ resource.fields.forEach((field) => {
19010
+ for (const roleId of field.readableBy)
19011
+ if (!has2(roleIds, roleId))
19012
+ addIssue(issues, { code: "field.unknown-role", severity: "error", message: `Field "${field.label}" references unknown readable role "${roleId}".`, path: `resources.${resource.id}.fields.${field.id}.readableBy` });
19013
+ for (const roleId of field.writableBy)
19014
+ if (!has2(roleIds, roleId))
19015
+ addIssue(issues, { code: "field.unknown-role", severity: "error", message: `Field "${field.label}" references unknown writable role "${roleId}".`, path: `resources.${resource.id}.fields.${field.id}.writableBy` });
19016
+ for (const workflowId of field.usedByWorkflowIds)
19017
+ if (!has2(workflowIds, workflowId))
19018
+ addIssue(issues, { code: "field.unknown-workflow", severity: "error", message: `Field "${field.label}" references unknown workflow "${workflowId}".`, path: `resources.${resource.id}.fields.${field.id}.usedByWorkflowIds` });
19019
+ });
18970
19020
  }
18971
19021
  for (const surface of plan.surfaces) {
18972
- reviewSurface(surface, { workflowIds, actionIds, resourceIds }, issues);
19022
+ reviewSurface(surface, { workflowIds, actionIds, resourceIds, roleIds, surfaceIds }, issues);
18973
19023
  }
19024
+ const visitingSurfaces = new Set;
19025
+ const visitedSurfaces = new Set;
19026
+ const visitSurface = (id) => {
19027
+ if (visitingSurfaces.has(id)) {
19028
+ addIssue(issues, { code: "surface.cycle", severity: "error", message: `Surface hierarchy contains a cycle at "${id}".`, path: `surfaces.${id}.parentSurfaceId` });
19029
+ return;
19030
+ }
19031
+ if (visitedSurfaces.has(id))
19032
+ return;
19033
+ visitingSurfaces.add(id);
19034
+ const parentId = plan.surfaces.find((surface) => surface.id === id)?.parentSurfaceId;
19035
+ if (parentId !== undefined && surfaceIds.has(parentId))
19036
+ visitSurface(parentId);
19037
+ visitingSurfaces.delete(id);
19038
+ visitedSurfaces.add(id);
19039
+ };
19040
+ for (const surface of plan.surfaces)
19041
+ visitSurface(surface.id);
18974
19042
  const menuById = new Map(plan.menu.map((item) => [item.id, item]));
18975
19043
  for (const item of plan.menu) {
18976
19044
  if (item.parentId !== undefined && !menuById.has(item.parentId)) {
@@ -19002,12 +19070,21 @@ function reviewProductPlan(plan) {
19002
19070
  addIssue(issues, { code: "menu.unknown-surface", severity: "error", message: `Menu item "${item.label}" references unknown surface.`, path: `menu.${item.id}.surfaceId` });
19003
19071
  if (item.resourceId && !has2(resourceIds, item.resourceId))
19004
19072
  addIssue(issues, { code: "menu.unknown-resource", severity: "error", message: `Menu item "${item.label}" references unknown resource.`, path: `menu.${item.id}.resourceId` });
19073
+ for (const workflowId of item.workflowIds)
19074
+ if (!has2(workflowIds, workflowId))
19075
+ addIssue(issues, { code: "menu.unknown-workflow", severity: "error", message: `Menu item "${item.label}" references unknown workflow "${workflowId}".`, path: `menu.${item.id}.workflowIds` });
19076
+ for (const roleId of item.roleIds)
19077
+ if (!has2(roleIds, roleId))
19078
+ addIssue(issues, { code: "menu.unknown-role", severity: "error", message: `Menu item "${item.label}" references unknown role "${roleId}".`, path: `menu.${item.id}.roleIds` });
19005
19079
  if (!item.roleIds.length && item.placement !== "hidden")
19006
19080
  addIssue(issues, { code: "menu.public-entry", severity: "info", message: `Menu item "${item.label}" is public at the UI hint layer.`, path: `menu.${item.id}.roleIds`, suggestion: "Keep server authorization independent from menu visibility." });
19007
19081
  }
19008
19082
  for (const action of plan.actions) {
19009
19083
  if (!has2(workflowIds, action.workflowId))
19010
19084
  addIssue(issues, { code: "action.unknown-workflow", severity: "error", message: `Action "${action.label}" references unknown workflow.`, path: `actions.${action.id}.workflowId` });
19085
+ for (const roleId of action.roleIds)
19086
+ if (!has2(roleIds, roleId))
19087
+ addIssue(issues, { code: "action.unknown-role", severity: "error", message: `Action "${action.label}" references unknown role "${roleId}".`, path: `actions.${action.id}.roleIds` });
19011
19088
  if (!action.roleIds.length)
19012
19089
  addIssue(issues, { code: "action.missing-roles", severity: "warning", message: `Action "${action.label}" has no role visibility contract.`, path: `actions.${action.id}.roleIds` });
19013
19090
  }
@@ -19036,6 +19113,11 @@ function reviewProductPlan(plan) {
19036
19113
  function reviewSurface(surface, refs, issues) {
19037
19114
  if (!surface.purpose.trim())
19038
19115
  addIssue(issues, { code: "surface.missing-purpose", severity: "error", message: `Surface "${surface.name}" has no purpose.`, path: `surfaces.${surface.id}.purpose` });
19116
+ for (const roleId of surface.roleIds)
19117
+ if (!refs.roleIds.has(roleId))
19118
+ addIssue(issues, { code: "surface.unknown-role", severity: "error", message: `Surface "${surface.name}" references unknown role "${roleId}".`, path: `surfaces.${surface.id}.roleIds` });
19119
+ if (surface.parentSurfaceId !== undefined && !refs.surfaceIds.has(surface.parentSurfaceId))
19120
+ addIssue(issues, { code: "surface.unknown-parent", severity: "error", message: `Surface "${surface.name}" references unknown parent surface "${surface.parentSurfaceId}".`, path: `surfaces.${surface.id}.parentSurfaceId` });
19039
19121
  if (!surface.workflowIds.length)
19040
19122
  addIssue(issues, { code: "surface.no-workflow", severity: "warning", message: `Surface "${surface.name}" is not attached to a workflow.`, path: `surfaces.${surface.id}.workflowIds` });
19041
19123
  surface.workflowIds.forEach((id) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/create",
3
- "version": "0.41.0",
3
+ "version": "0.41.1",
4
4
  "description": "Scaffolding tool for svadmin projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,7 +30,7 @@
30
30
  "@types/node": "^26.4.0",
31
31
  "@types/prompts": "^2.4.9",
32
32
  "typescript": "^7.0.2",
33
- "@svadmin/core": "^0.61.0"
33
+ "@svadmin/core": "^0.61.1"
34
34
  },
35
35
  "keywords": [
36
36
  "svadmin",
@@ -10,10 +10,10 @@
10
10
  "check": "svelte-check --tsconfig ./tsconfig.json"
11
11
  },
12
12
  "dependencies": {
13
- "@svadmin/core": "^0.61.0",
14
- "@svadmin/app": "^0.2.4",
15
- "@svadmin/ai-elements": "^0.11.2",
16
- "@svadmin/ui": "^0.84.0",
13
+ "@svadmin/core": "^0.61.1",
14
+ "@svadmin/app": "^0.2.5",
15
+ "@svadmin/ai-elements": "^0.11.3",
16
+ "@svadmin/ui": "^0.84.1",
17
17
  "typebox": "^1.3.34",
18
18
  "@tanstack/svelte-query": "^6.1.48",
19
19
  "@lucide/svelte": "^1.35.0"
@@ -42,76 +42,76 @@
42
42
  "graphql": "^16.8.0"
43
43
  },
44
44
  "simple-rest": {
45
- "@svadmin/simple-rest": "^0.12.5",
45
+ "@svadmin/simple-rest": "^0.12.6",
46
46
  "@refinedev/simple-rest": "^6.0.1"
47
47
  },
48
48
  "supabase": {
49
- "@svadmin/supabase": "^0.19.2",
49
+ "@svadmin/supabase": "^0.19.3",
50
50
  "@supabase/supabase-js": "^2.112.4",
51
51
  "@refinedev/supabase": "^6.0.2"
52
52
  },
53
53
  "graphql": {
54
- "@svadmin/graphql": "^0.12.5",
54
+ "@svadmin/graphql": "^0.12.6",
55
55
  "@refinedev/graphql": "^8.0.1",
56
56
  "graphql-request": "^7.4.0",
57
57
  "graphql": "^16.8.0"
58
58
  },
59
59
  "rest": {
60
- "@svadmin/rest": "^0.2.4",
60
+ "@svadmin/rest": "^0.2.5",
61
61
  "@refinedev/rest": "^2.1.1",
62
62
  "@nestjsx/crud-request": "^5.0.0-alpha.3"
63
63
  },
64
64
  "airtable": {
65
- "@svadmin/airtable": "^0.12.5",
65
+ "@svadmin/airtable": "^0.12.6",
66
66
  "@refinedev/airtable": "^5.0.1"
67
67
  },
68
68
  "appwrite": {
69
- "@svadmin/appwrite": "^0.11.5",
69
+ "@svadmin/appwrite": "^0.11.6",
70
70
  "appwrite": ">=26.2.0",
71
71
  "@refinedev/appwrite": "^8.0.1"
72
72
  },
73
73
  "directus": {
74
- "@svadmin/directus": "^0.11.5"
74
+ "@svadmin/directus": "^0.11.6"
75
75
  },
76
76
  "drizzle": {
77
- "@svadmin/drizzle": "^0.14.2",
77
+ "@svadmin/drizzle": "^0.14.3",
78
78
  "drizzle-orm": "^1.0.0-rc.1",
79
79
  "refine-sqlx": "^0.10.3"
80
80
  },
81
81
  "elysia": {
82
- "@svadmin/elysia": "^0.14.5",
82
+ "@svadmin/elysia": "^0.14.6",
83
83
  "elysia": ">=1.4.30",
84
84
  "@elysiajs/eden": ">=1.4.9"
85
85
  },
86
86
  "firebase": {
87
- "@svadmin/firebase": "^0.12.5"
87
+ "@svadmin/firebase": "^0.12.6"
88
88
  },
89
89
  "hasura": {
90
- "@svadmin/hasura": "^0.12.5",
90
+ "@svadmin/hasura": "^0.12.6",
91
91
  "@refinedev/hasura": "^7.0.1",
92
92
  "graphql-request": "^7.4.0"
93
93
  },
94
94
  "medusa": {
95
- "@svadmin/medusa": "^0.12.5",
95
+ "@svadmin/medusa": "^0.12.6",
96
96
  "@refinedev/medusa": "^5.0.0"
97
97
  },
98
98
  "nestjs-query": {
99
- "@svadmin/nestjs-query": "^0.12.5",
99
+ "@svadmin/nestjs-query": "^0.12.6",
100
100
  "@refinedev/nestjs-query": "^5.0.0"
101
101
  },
102
102
  "nestjsx-crud": {
103
- "@svadmin/nestjsx-crud": "^0.12.5",
103
+ "@svadmin/nestjsx-crud": "^0.12.6",
104
104
  "@refinedev/nestjsx-crud": "^6.0.1"
105
105
  },
106
106
  "pocketbase": {
107
- "@svadmin/pocketbase": "^0.13.2",
107
+ "@svadmin/pocketbase": "^0.13.3",
108
108
  "pocketbase": ">=0.28.0"
109
109
  },
110
110
  "sanity": {
111
- "@svadmin/sanity": "^0.11.5"
111
+ "@svadmin/sanity": "^0.11.6"
112
112
  },
113
113
  "strapi": {
114
- "@svadmin/strapi": "^0.12.5",
114
+ "@svadmin/strapi": "^0.12.6",
115
115
  "@refinedev/strapi-v4": "^7.0.1"
116
116
  }
117
117
  },