@james_sww/metaform-asset-distribution 0.1.0 → 0.1.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.
- package/README.md +3 -3
- package/codebase/codebase.json +9 -8
- package/codebase/template/.env.example +11 -1
- package/codebase/template/src/components/common/empty-state.tsx +52 -0
- package/codebase/template/src/components/common/error-boundary.tsx +2 -2
- package/codebase/template/src/components/ui/alert.tsx +48 -50
- package/codebase/template/src/hooks/index.ts +2 -2
- package/codebase/template/src/integrations/platform.ts +65 -1
- package/codebase/template/src/lib/http-client.ts +1 -1
- package/codebase/template/src/main.tsx +11 -1
- package/dist/cli.js +1 -1
- package/dist/template.d.ts +1 -1
- package/dist/template.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
发布版本使用 Node 24.18.0、pnpm 10.18.2。CLI 自带固定版本模板与源码 Registry,无需平台仓库或本机桌面目录。私有包需有下载权限;沙箱凭据由运行环境注入,不写入应用源码。
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
npm install -g @james_sww/metaform-asset-distribution@0.1.
|
|
10
|
+
npm install -g @james_sww/metaform-asset-distribution@0.1.1
|
|
11
11
|
metaform-assets init --project ./my-app
|
|
12
12
|
cd my-app
|
|
13
13
|
corepack pnpm install --frozen-lockfile
|
|
@@ -16,7 +16,7 @@ corepack pnpm install --frozen-lockfile
|
|
|
16
16
|
不安装全局工具也可运行:
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
npm exec --yes --package=@james_sww/metaform-asset-distribution@0.1.
|
|
19
|
+
npm exec --yes --package=@james_sww/metaform-asset-distribution@0.1.1 -- metaform-assets init --project ./my-app
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
Agent 根据应用本体和 skill 形成 `ApplicationDesign` JSON 后,在已初始化的应用目录执行:
|
|
@@ -31,7 +31,7 @@ corepack pnpm dev
|
|
|
31
31
|
|
|
32
32
|
CLI 负责按底稿确定性装配;读取线上本体并作语义转译由 Agent 完成。匹配组件安装为应用内源码,未匹配区域保留语义骨架。模板初始化、安装成功不代表真实业务数据已绑定或平台 S7 已自动接通。
|
|
33
33
|
|
|
34
|
-
基础模板也单独按 `@james_sww/metaform-codebase-template@0.1.
|
|
34
|
+
基础模板也单独按 `@james_sww/metaform-codebase-template@0.1.1` 分发,可安装到工具目录后用 `--codebase /path/to/node_modules/@james_sww/metaform-codebase-template` 指定。初始化底稿的完整类型随 CLI 发布在 `dist/application-design.d.ts`,语义区域类型在 `dist/generated/semantic-region-contract.d.ts`。固定 CLI、模板和 Registry 版本用于沙箱重放;初始化后的源码和基线存入项目快照。
|
|
35
35
|
|
|
36
36
|
## 维护仓库
|
|
37
37
|
|
package/codebase/codebase.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"name": "@james_sww/metaform-codebase-template",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.1",
|
|
5
5
|
"files": {
|
|
6
6
|
"_gitignore": "184ad45835c68a6d3fd5100366678797c5fe4d2a2aa6548749039912376faf54",
|
|
7
|
-
".env.example": "
|
|
7
|
+
".env.example": "602ae0c89a09593ef560b5d1f7010439e228c7fb0dd7bbc69c9655cf82a8944f",
|
|
8
8
|
".metaform/asset-baselines/0551d7ee067550cd2757cf693414c5e180adbb5efe3af4f5f9b1242aa9cd788e": "0551d7ee067550cd2757cf693414c5e180adbb5efe3af4f5f9b1242aa9cd788e",
|
|
9
9
|
".metaform/asset-baselines/0e7bb494140c2c4118b919496a4246b26935367453282bd654fedecd2b16f1a5": "0e7bb494140c2c4118b919496a4246b26935367453282bd654fedecd2b16f1a5",
|
|
10
10
|
".metaform/asset-baselines/3d1a9a8a1f7ed83eaf3422d1497cac02b643fe1c470b5c5f357f3bd26c585a0f": "3d1a9a8a1f7ed83eaf3422d1497cac02b643fe1c470b5c5f357f3bd26c585a0f",
|
|
@@ -33,10 +33,11 @@
|
|
|
33
33
|
"pnpm-lock.yaml": "46912c695ef4a2f351a7469aa26ab1312ccf6347b4d0aa2b075bf3a96632782b",
|
|
34
34
|
"README.md": "d738a1bfbe9e9221f15e2bdcb46af9fae112afd0c1354bd9d45bdf3ae5205e38",
|
|
35
35
|
"role-targets.json": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570",
|
|
36
|
-
"src/components/common/
|
|
36
|
+
"src/components/common/empty-state.tsx": "6008a3193b715c5453eee3dba6ee20608fca3e6cf8bba9af774ba898a1890fea",
|
|
37
|
+
"src/components/common/error-boundary.tsx": "1fef1c92e27d8206448cb0e1840bfe81826a95fc58b1ec4c1e2f14462293bf5b",
|
|
37
38
|
"src/components/common/semantic-region/contract.ts": "92cd18a919b549b4827f611c3a4157652c4a6ae034850da1254ca422693c759d",
|
|
38
39
|
"src/components/common/semantic-region/semantic-region-scaffold.tsx": "567f039384fb23a20a4cd5e1d4fda56255abda6fc04d80b36c4bb6f6c040691b",
|
|
39
|
-
"src/components/ui/alert.tsx": "
|
|
40
|
+
"src/components/ui/alert.tsx": "9c380bb1703ba96fe873d5b3094153fa3402d305719e60aa733e4ab202ffddaa",
|
|
40
41
|
"src/components/ui/avatar.tsx": "e9d4784c2ad5bceccdc4f2f19f367fdc37537a10e3f99f510e42c0226adfbf8f",
|
|
41
42
|
"src/components/ui/badge.tsx": "bf9e1bbb252c1fa5da20a17eeee1ac163c7d3a0b6deff817d30ba04b766fb8a1",
|
|
42
43
|
"src/components/ui/breadcrumb.tsx": "c75fcd3d9e8470188bc0dd4b27a2b26fff3ad42b9c9c534a953dc090ae2cba33",
|
|
@@ -59,16 +60,16 @@
|
|
|
59
60
|
"src/components/ui/tabs.tsx": "e1df6e2f95ece373ca909c1d54b8ba8cf4c47dd425c1784bbb9a01c38dd0936b",
|
|
60
61
|
"src/components/ui/tooltip.tsx": "5f56cd15afc07567f7b80b8b4d9e89da405e070a562583bbbc2414fd981af324",
|
|
61
62
|
"src/contexts/toast-context.tsx": "a3af91f8915d7753ef15888997ba941d07459d4df3e3e6d58e25365143bf336f",
|
|
62
|
-
"src/hooks/index.ts": "
|
|
63
|
-
"src/integrations/platform.ts": "
|
|
63
|
+
"src/hooks/index.ts": "ddfe80f0fc8b0b49c0b31f27d9aadc4a54715b5ccee40996a205ecec36958b79",
|
|
64
|
+
"src/integrations/platform.ts": "6edb9755c91f6bf1f16815911739f64c4f45405948eaf0d7bbd5cd2fa939dbe9",
|
|
64
65
|
"src/layouts/role-workspace-account.tsx": "3d1a9a8a1f7ed83eaf3422d1497cac02b643fe1c470b5c5f357f3bd26c585a0f",
|
|
65
66
|
"src/layouts/role-workspace-icons.tsx": "4b8ea69ef28a9699c0c3b28e12590a7521f91aa1a4a3d618fc0778455d39cbcc",
|
|
66
67
|
"src/layouts/role-workspace-routes.ts": "0551d7ee067550cd2757cf693414c5e180adbb5efe3af4f5f9b1242aa9cd788e",
|
|
67
68
|
"src/layouts/role-workspace-shell.tsx": "bcc48c05cd912dad5373242ff699cdbec032c8ebb2de45a9df5de79a6daec59c",
|
|
68
69
|
"src/layouts/role-workspace-types.ts": "b9e31158082ce71a2b3bdb904d9462619e3690735e73e138b1ce57f8f036347f",
|
|
69
|
-
"src/lib/http-client.ts": "
|
|
70
|
+
"src/lib/http-client.ts": "a284532dd0bcc18aaf444b92e883ec77750515c1ba01626d8fa96c3e540d2e37",
|
|
70
71
|
"src/lib/utils.ts": "4acbc7165a8d54738ff62b51414e772c08fe78434e524e6d8770180d3ba2925f",
|
|
71
|
-
"src/main.tsx": "
|
|
72
|
+
"src/main.tsx": "6fa2b2eb510ef94ca04b894c573a041419191f6896043f227eb160ab49bd6efa",
|
|
72
73
|
"src/styles/index.css": "72f8f255c56d117bab6f2aaa53ce5a37aa9b2d5d085b321b9fcb4b4b983e15eb",
|
|
73
74
|
"src/styles/styles.css": "b48c73f51ff3e148f20ad2c9dbd2847235e4fdf1cdb304d5e2feec9c2febd65e",
|
|
74
75
|
"src/styles/theme-tokens.css": "0e7bb494140c2c4118b919496a4246b26935367453282bd654fedecd2b16f1a5",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { InboxIcon } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
import { Button } from '../ui/button';
|
|
6
|
+
|
|
7
|
+
export interface EmptyStateProps {
|
|
8
|
+
icon?: ReactNode;
|
|
9
|
+
title: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
action?: {
|
|
12
|
+
label: string;
|
|
13
|
+
onClick: () => void;
|
|
14
|
+
};
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 空状态组件
|
|
20
|
+
* 当列表、表格或其他数据容器没有内容时显示
|
|
21
|
+
*/
|
|
22
|
+
export function EmptyState({
|
|
23
|
+
icon,
|
|
24
|
+
title,
|
|
25
|
+
description,
|
|
26
|
+
action,
|
|
27
|
+
className,
|
|
28
|
+
}: EmptyStateProps) {
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
className={cn(
|
|
32
|
+
'flex min-h-[400px] flex-col items-center justify-center gap-4 rounded-lg border border-dashed border-border bg-muted/30 p-8 text-center',
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
>
|
|
36
|
+
<div className="flex size-20 items-center justify-center rounded-full bg-muted text-muted-foreground">
|
|
37
|
+
{icon || <InboxIcon className="size-10" />}
|
|
38
|
+
</div>
|
|
39
|
+
<div className="space-y-2">
|
|
40
|
+
<h3 className="text-lg font-semibold text-foreground">{title}</h3>
|
|
41
|
+
{description && (
|
|
42
|
+
<p className="max-w-md text-sm text-muted-foreground">{description}</p>
|
|
43
|
+
)}
|
|
44
|
+
</div>
|
|
45
|
+
{action && (
|
|
46
|
+
<Button onClick={action.onClick} className="mt-2">
|
|
47
|
+
{action.label}
|
|
48
|
+
</Button>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { Component, type ErrorInfo, type ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
-
import { Button } from '../
|
|
4
|
-
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../
|
|
3
|
+
import { Button } from '../ui/button';
|
|
4
|
+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../ui/card';
|
|
5
5
|
|
|
6
6
|
interface ErrorBoundaryProps {
|
|
7
7
|
children: ReactNode;
|
|
@@ -1,65 +1,63 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
-
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
4
5
|
|
|
5
6
|
const alertVariants = cva(
|
|
6
|
-
"relative
|
|
7
|
+
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
|
7
8
|
{
|
|
8
9
|
variants: {
|
|
9
10
|
variant: {
|
|
10
|
-
default: "bg-
|
|
11
|
+
default: "bg-background text-foreground",
|
|
12
|
+
success:
|
|
13
|
+
"border-semantic-success/50 bg-semantic-success-soft text-semantic-success-foreground [&>svg]:text-semantic-success",
|
|
14
|
+
warning:
|
|
15
|
+
"border-semantic-warning/50 bg-semantic-warning-soft text-semantic-warning-foreground [&>svg]:text-semantic-warning",
|
|
11
16
|
destructive:
|
|
12
|
-
"bg-
|
|
17
|
+
"border-semantic-danger/50 bg-semantic-danger-soft text-semantic-danger-foreground [&>svg]:text-semantic-danger",
|
|
13
18
|
},
|
|
14
19
|
},
|
|
15
20
|
defaultVariants: {
|
|
16
21
|
variant: "default",
|
|
17
22
|
},
|
|
18
|
-
}
|
|
19
|
-
)
|
|
23
|
+
},
|
|
24
|
+
);
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
)
|
|
34
|
-
}
|
|
26
|
+
const Alert = React.forwardRef<
|
|
27
|
+
HTMLDivElement,
|
|
28
|
+
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
|
29
|
+
>(({ className, variant, ...props }, ref) => (
|
|
30
|
+
<div
|
|
31
|
+
ref={ref}
|
|
32
|
+
role="alert"
|
|
33
|
+
className={cn(alertVariants({ variant }), className)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
));
|
|
37
|
+
Alert.displayName = "Alert";
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
39
|
+
const AlertTitle = React.forwardRef<
|
|
40
|
+
HTMLParagraphElement,
|
|
41
|
+
React.HTMLAttributes<HTMLHeadingElement>
|
|
42
|
+
>(({ className, ...props }, ref) => (
|
|
43
|
+
<h5
|
|
44
|
+
ref={ref}
|
|
45
|
+
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
));
|
|
49
|
+
AlertTitle.displayName = "AlertTitle";
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
{...props}
|
|
61
|
-
/>
|
|
62
|
-
)
|
|
63
|
-
}
|
|
51
|
+
const AlertDescription = React.forwardRef<
|
|
52
|
+
HTMLParagraphElement,
|
|
53
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
54
|
+
>(({ className, ...props }, ref) => (
|
|
55
|
+
<div
|
|
56
|
+
ref={ref}
|
|
57
|
+
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
));
|
|
61
|
+
AlertDescription.displayName = "AlertDescription";
|
|
64
62
|
|
|
65
|
-
export { Alert, AlertTitle, AlertDescription }
|
|
63
|
+
export { Alert, AlertTitle, AlertDescription };
|
|
@@ -166,8 +166,8 @@ export function useMediaQuery(query: string): boolean {
|
|
|
166
166
|
*/
|
|
167
167
|
export function useClickOutside<T extends HTMLElement = HTMLElement>(
|
|
168
168
|
handler: () => void,
|
|
169
|
-
): React.RefObject<T> {
|
|
170
|
-
const ref = useRef<T>(null);
|
|
169
|
+
): React.RefObject<T | null> {
|
|
170
|
+
const ref = useRef<T | null>(null);
|
|
171
171
|
|
|
172
172
|
useEffect(() => {
|
|
173
173
|
const listener = (event: MouseEvent | TouchEvent) => {
|
|
@@ -1,6 +1,70 @@
|
|
|
1
|
-
|
|
1
|
+
import { httpClient } from '../lib/http-client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 平台集成接口
|
|
5
|
+
* 提供统一的认证、查询和操作能力
|
|
6
|
+
*/
|
|
2
7
|
export interface PlatformIntegration {
|
|
3
8
|
getSession(): Promise<{ actorId: string } | null>;
|
|
4
9
|
query<T>(operation: string, input: unknown): Promise<T>;
|
|
5
10
|
execute<T>(operation: string, input: unknown): Promise<T>;
|
|
6
11
|
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 默认平台集成实现
|
|
15
|
+
* 使用 HTTP 客户端与后端通信
|
|
16
|
+
*/
|
|
17
|
+
class DefaultPlatformIntegration implements PlatformIntegration {
|
|
18
|
+
async getSession(): Promise<{ actorId: string } | null> {
|
|
19
|
+
try {
|
|
20
|
+
const response = await httpClient.get<{ actorId: string }>('/auth/session');
|
|
21
|
+
return response.data;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.warn('Failed to get session:', error);
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async query<T>(operation: string, input: unknown): Promise<T> {
|
|
29
|
+
const response = await httpClient.post<T>('/query', {
|
|
30
|
+
operation,
|
|
31
|
+
input,
|
|
32
|
+
});
|
|
33
|
+
return response.data;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async execute<T>(operation: string, input: unknown): Promise<T> {
|
|
37
|
+
const response = await httpClient.post<T>('/execute', {
|
|
38
|
+
operation,
|
|
39
|
+
input,
|
|
40
|
+
});
|
|
41
|
+
return response.data;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Mock 平台集成(用于开发和测试)
|
|
47
|
+
*/
|
|
48
|
+
class MockPlatformIntegration implements PlatformIntegration {
|
|
49
|
+
async getSession(): Promise<{ actorId: string } | null> {
|
|
50
|
+
// 模拟已登录用户
|
|
51
|
+
return { actorId: 'mock-user-001' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async query<T>(operation: string, input: unknown): Promise<T> {
|
|
55
|
+
console.log('[Mock Query]', operation, input);
|
|
56
|
+
// 返回模拟数据
|
|
57
|
+
return {} as T;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async execute<T>(operation: string, input: unknown): Promise<T> {
|
|
61
|
+
console.log('[Mock Execute]', operation, input);
|
|
62
|
+
return {} as T;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 根据环境变量选择实现
|
|
67
|
+
const useMock = (import.meta as { env?: { VITE_USE_MOCK_API?: string } }).env?.VITE_USE_MOCK_API === 'true';
|
|
68
|
+
export const platform: PlatformIntegration = useMock
|
|
69
|
+
? new MockPlatformIntegration()
|
|
70
|
+
: new DefaultPlatformIntegration();
|
|
@@ -228,7 +228,7 @@ export class HttpClient {
|
|
|
228
228
|
|
|
229
229
|
// 创建默认实例
|
|
230
230
|
export const httpClient = new HttpClient({
|
|
231
|
-
baseURL: import.meta.env
|
|
231
|
+
baseURL: (import.meta as { env?: { VITE_API_BASE_URL?: string } }).env?.VITE_API_BASE_URL || '/api',
|
|
232
232
|
onRequest: async (config) => {
|
|
233
233
|
// 自动添加认证 token
|
|
234
234
|
const token = localStorage.getItem('auth_token');
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { createRoot } from "react-dom/client";
|
|
2
|
+
import { Toaster } from "sonner";
|
|
2
3
|
|
|
4
|
+
import { ErrorBoundary } from "./components/common/error-boundary";
|
|
5
|
+
import { ToastProvider } from "./contexts/toast-context";
|
|
3
6
|
import { DefaultWorkspace } from "./workspace/default-workspace";
|
|
4
7
|
import "./styles/index.css";
|
|
5
8
|
|
|
6
|
-
createRoot(document.getElementById("root")!).render(
|
|
9
|
+
createRoot(document.getElementById("root")!).render(
|
|
10
|
+
<ErrorBoundary>
|
|
11
|
+
<ToastProvider>
|
|
12
|
+
<DefaultWorkspace />
|
|
13
|
+
<Toaster position="top-right" richColors closeButton />
|
|
14
|
+
</ToastProvider>
|
|
15
|
+
</ErrorBoundary>,
|
|
16
|
+
);
|
package/dist/cli.js
CHANGED
|
@@ -96,7 +96,7 @@ export async function runCLI(argv, io) {
|
|
|
96
96
|
else if (cmd === 'init')
|
|
97
97
|
output = await initProject({
|
|
98
98
|
projectRoot: root,
|
|
99
|
-
templateVersion: options.get('template')
|
|
99
|
+
templateVersion: options.get('template'),
|
|
100
100
|
codebaseRoot: options.get('codebase'),
|
|
101
101
|
});
|
|
102
102
|
else if (cmd === 'reconcile')
|
package/dist/template.d.ts
CHANGED
package/dist/template.js
CHANGED
|
@@ -22,7 +22,7 @@ export async function initProject(input) {
|
|
|
22
22
|
Array.isArray(manifest.files) ||
|
|
23
23
|
typeof manifest.files !== 'object')
|
|
24
24
|
throw new DistributionError('invalid_codebase');
|
|
25
|
-
if (manifest.version !== input.templateVersion)
|
|
25
|
+
if (input.templateVersion && manifest.version !== input.templateVersion)
|
|
26
26
|
throw new DistributionError('template_version');
|
|
27
27
|
const template = validateTargetPath(packageRoot, 'template');
|
|
28
28
|
const actual = await projectFiles(template);
|