create-windy 0.6.8 → 0.6.9
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/cli.js +152 -24
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/README.md +4 -2
- package/template/apps/server/src/runtime-development.ts +1 -0
- package/template/apps/web/src/layout/AppSidebar.vue +1 -0
- package/template/apps/web/src/pages/ui/UiComponentPage.vue +7 -0
- package/template/apps/web/src/pages/ui/UiComponentPage.webtest.ts +94 -0
- package/template/apps/web/src/pages/ui/component-catalog.ts +248 -0
- package/template/apps/web/src/pages/ui/components/ComponentPreviewCard.vue +50 -0
- package/template/apps/web/src/pages/ui/components/DataComponentShowcases.vue +131 -0
- package/template/apps/web/src/pages/ui/components/FeedbackComponentShowcases.vue +158 -0
- package/template/apps/web/src/pages/ui/components/FoundationComponentShowcases.vue +129 -0
- package/template/apps/web/src/pages/ui/components/NavigationComponentShowcases.vue +155 -0
- package/template/apps/web/src/pages/ui/components/ProjectComponentShowcases.vue +31 -0
- package/template/apps/web/src/pages/ui/components/UiComponentGallery.vue +238 -0
- package/template/apps/web/src/router/index.ts +2 -0
- package/template/apps/web/src/router/manifest-routes.webtest.ts +1 -0
- package/template/apps/web/src/router/platform-admin-route-bindings.ts +3 -1
- package/template/docs/development/business-web-routes.md +3 -1
- package/template/docs/development/ui-component-gallery.md +44 -0
- package/template/docs/operations/incremental-delivery.md +10 -9
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/system-admin-routes.ts +8 -0
- package/template/packages/modules/src/system-admin-ui-catalog.ts +1 -1
- package/template/packages/modules/src/system-features.ts +5 -0
- package/template/packages/modules/src/system-module-catalog.ts +2 -1
- package/template/packages/modules/src/system-modules.ts +11 -0
- package/template/packages/modules/src/system-permissions.ts +2 -0
- package/template/packages/modules/src/system-ui-components.test.ts +36 -0
package/dist/cli.js
CHANGED
|
@@ -3480,7 +3480,8 @@ browser-safe \`./admin\` \u4E0E project-owned \u7684
|
|
|
3480
3480
|
\`apps/web/src/app/web-composition.ts\` \u88C5\u914D\u9875\u9762\uFF0C\u4E0D\u590D\u5236\u7B2C\u4E8C\u4EFD Feature\u3001Permission
|
|
3481
3481
|
\u6216\u8DEF\u7531\u6E05\u5355\u3002\u6574\u4E2A \`apps/web/src\` \u4E0E\u6574\u4E2A \`apps/server/src/app\` \u90FD\u662F
|
|
3482
3482
|
project-owned\uFF0C\u5BA2\u6237\u53EF\u4EE5\u4FEE\u6539 Router\u3001layout\u3001\u9875\u9762\u3001Host \u548C Server \u7EC4\u5408\uFF1B\u666E\u901A
|
|
3483
|
-
\`windy update\` \u4E0D\u4F1A\u8986\u76D6\u6216\u4E09\u65B9\u5408\u5E76\u8FD9\u4E9B\u6587\u4EF6\u3002
|
|
3483
|
+
\`windy update\` \u4E0D\u4F1A\u6574\u6587\u4EF6\u8986\u76D6\u6216\u4E09\u65B9\u5408\u5E76\u8FD9\u4E9B\u6587\u4EF6\u3002\u7248\u672C recipe \u53EA\u6709\u5728\u663E\u5F0F\u767B\u8BB0
|
|
3484
|
+
path \u4E0E\u786E\u5B9A\u6027\u8BED\u4E49\u951A\u70B9\u540E\u624D\u80FD\u505A\u5E42\u7B49\u8FFD\u52A0\uFF1B\u951A\u70B9\u7F3A\u5931\u6216\u4E0D\u552F\u4E00\u65F6\u5FC5\u987B\u5728\u5199\u5165\u524D\u505C\u6B62\u3002
|
|
3484
3485
|
|
|
3485
3486
|
\u7CBE\u786E\u751F\u6210\u6765\u6E90\u4FDD\u7559\u5728 \`.windy/generation.json\`\u3002\u5F53\u524D\u7248\u672C\u5DF2\u63D0\u4F9B\u53EF\u56DE\u6EDA\u7684
|
|
3486
3487
|
\`windy update\` \u4E0E \`windy module list/enable/disable\`\u3002\u5F53\u524D\u5185\u7F6E\u6A21\u5757\u4ECD\u4F9D\u8D56
|
|
@@ -3514,7 +3515,8 @@ ${includeOxc ? "\u63D0\u4EA4\u65F6 Husky \u4F1A\u901A\u8FC7 lint-staged \u4E0E O
|
|
|
3514
3515
|
\u66F4\u65B0\u524D\u65E0\u9700\u63D0\u4EA4\u3001\u6682\u5B58\u6216\u6E05\u7406\u4E1A\u52A1\u6539\u52A8\u3002\`--dry-run\` \u53EA\u751F\u6210\u8BA1\u5212\uFF0C\u4E0D\u5199\u9879\u76EE\uFF1B\u65E0\u5173 WIP
|
|
3515
3516
|
\u4FDD\u6301\u539F\u6837\uFF0C\u547D\u4E2D\u540C\u4E00\u8DEF\u5F84\u65F6\u6309\u51BB\u7ED3\u5FEB\u7167\u4E0E\u4E09\u65B9\u5408\u5E76 fail closed\u3002\u6B63\u5F0F\u66F4\u65B0\u5148\u5907\u4EFD\uFF0C\u518D\u505A
|
|
3516
3517
|
package \u5B57\u6BB5\u7EA7\u5408\u5E76\u3001\u5171\u4EAB\u811A\u624B\u67B6\u4E09\u65B9\u5408\u5E76\u4E0E\u5B8C\u6574\u9A8C\u8BC1\u3002\u6574\u4E2A \`apps/web/src\` \u548C
|
|
3517
|
-
\`apps/server/src/app\` \u4FDD\u6301\u9879\u76EE\u7248\u672C\
|
|
3518
|
+
\`apps/server/src/app\` \u9ED8\u8BA4\u4FDD\u6301\u9879\u76EE\u7248\u672C\uFF1B\u5DF2\u767B\u8BB0\u7248\u672C recipe \u53EA\u4FEE\u6539\u5176\u58F0\u660E\u7684\u7A84\u951A\u70B9\u3002
|
|
3519
|
+
\u4EFB\u4F55\u51B2\u7A81\u90FD\u4F1A\u5728\u5199\u5165\u524D\u4E2D\u6B62\uFF0C\u6A21\u5757\u542F\u505C\u5728\u4EA4\u4E92
|
|
3518
3520
|
\u7EC8\u7AEF\u786E\u8BA4\uFF1BCI\u3001Agent \u6216\u5176\u5B83\u975E TTY \u73AF\u5883\u5FC5\u987B\u663E\u5F0F\u4F20\u5165 \`--yes\`\u3002
|
|
3519
3521
|
\u65E0\u5E76\u53D1\u53D8\u5316\u7684\u9A8C\u8BC1\u5931\u8D25\u4F1A\u81EA\u52A8\u56DE\u6EDA\uFF1B\u4E2D\u65AD\u6062\u590D\u4F7F\u7528
|
|
3520
3522
|
\`bun run windy doctor --repair\`\u3002\u8BE5\u547D\u4EE4\u5728\u63D0\u4EA4\u70B9\u524D\u4FDD\u7559\u5E76\u53D1\u7F16\u8F91\u5E76\u56DE\u6EDA\u5176\u4F59\u8DEF\u5F84\uFF0C
|
|
@@ -3694,6 +3696,7 @@ var templateDocumentationFiles = [
|
|
|
3694
3696
|
"docs/development/custom-error-pages.md",
|
|
3695
3697
|
"docs/development/custom-login-page.md",
|
|
3696
3698
|
"docs/development/module-lifecycle.md",
|
|
3699
|
+
"docs/development/ui-component-gallery.md",
|
|
3697
3700
|
"docs/license/license-offline-activation-code.md",
|
|
3698
3701
|
"docs/operations/observability.md",
|
|
3699
3702
|
"docs/operations/incremental-delivery.md",
|
|
@@ -7727,6 +7730,11 @@ var recipes = [
|
|
|
7727
7730
|
id: "starter-0.6.7-to-0.6.8",
|
|
7728
7731
|
from: "0.6.7",
|
|
7729
7732
|
to: "0.6.8"
|
|
7733
|
+
},
|
|
7734
|
+
{
|
|
7735
|
+
id: "starter-0.6.8-to-0.6.9",
|
|
7736
|
+
from: "0.6.8",
|
|
7737
|
+
to: "0.6.9"
|
|
7730
7738
|
}
|
|
7731
7739
|
];
|
|
7732
7740
|
function resolveRecipeChain(sourceVersion, targetVersion) {
|
|
@@ -9088,8 +9096,123 @@ function conflict(path2, reason, expectedProjectContent) {
|
|
|
9088
9096
|
};
|
|
9089
9097
|
}
|
|
9090
9098
|
|
|
9099
|
+
// src/update/recipe-project-owned.ts
|
|
9100
|
+
var managedPathsByRecipe = {
|
|
9101
|
+
"starter-0.6.8-to-0.6.9": [
|
|
9102
|
+
"apps/web/src/router/index.ts",
|
|
9103
|
+
"apps/web/src/router/platform-admin-route-bindings.ts"
|
|
9104
|
+
]
|
|
9105
|
+
};
|
|
9106
|
+
function recipeManagedProjectOwnedPaths(recipes2) {
|
|
9107
|
+
return new Set(recipes2.flatMap((recipe) => managedPathsByRecipe[recipe] ?? []));
|
|
9108
|
+
}
|
|
9109
|
+
async function planRecipeProjectOwned(input) {
|
|
9110
|
+
if (!input.ours) {
|
|
9111
|
+
return conflict2(input, "\u7248\u672C recipe \u6240\u9700\u7684\u9879\u76EE\u951A\u70B9\u7F3A\u5931");
|
|
9112
|
+
}
|
|
9113
|
+
const source = new TextDecoder().decode(input.ours);
|
|
9114
|
+
const transformed = transformProjectAdminAnchor(input.path, source);
|
|
9115
|
+
if ("error" in transformed)
|
|
9116
|
+
return conflict2(input, transformed.error);
|
|
9117
|
+
if (transformed.content === source) {
|
|
9118
|
+
return preserve(input, "\u5DF2\u5305\u542B\u7248\u672C recipe \u76EE\u6807\u5185\u5BB9");
|
|
9119
|
+
}
|
|
9120
|
+
return update(input, new TextEncoder().encode(transformed.content));
|
|
9121
|
+
}
|
|
9122
|
+
function transformProjectAdminAnchor(path2, source) {
|
|
9123
|
+
if (path2 === "apps/web/src/router/index.ts") {
|
|
9124
|
+
return addComponentPageLoader(source);
|
|
9125
|
+
}
|
|
9126
|
+
if (path2 === "apps/web/src/router/platform-admin-route-bindings.ts") {
|
|
9127
|
+
return addComponentRouteBinding(source);
|
|
9128
|
+
}
|
|
9129
|
+
return { error: `\u7248\u672C recipe \u672A\u767B\u8BB0\u9879\u76EE\u951A\u70B9\uFF1A${path2}` };
|
|
9130
|
+
}
|
|
9131
|
+
function addComponentPageLoader(source) {
|
|
9132
|
+
if (/^\s*"ui-components"\s*:/m.test(source))
|
|
9133
|
+
return { content: source };
|
|
9134
|
+
const declaration = source.indexOf("const platformAdminPageComponents");
|
|
9135
|
+
const start = source.indexOf("= {", declaration);
|
|
9136
|
+
const end = source.indexOf(`
|
|
9137
|
+
};`, start);
|
|
9138
|
+
if (declaration < 0 || start < 0 || end < 0) {
|
|
9139
|
+
return { error: "\u627E\u4E0D\u5230 platformAdminPageComponents \u9879\u76EE\u951A\u70B9" };
|
|
9140
|
+
}
|
|
9141
|
+
const block = source.slice(start, end);
|
|
9142
|
+
const profiles = [...block.matchAll(/^(\s*)profile:\s*.*,$/gm)];
|
|
9143
|
+
if (profiles.length !== 1) {
|
|
9144
|
+
return { error: "platformAdminPageComponents profile \u951A\u70B9\u4E0D\u552F\u4E00" };
|
|
9145
|
+
}
|
|
9146
|
+
const profile = profiles[0];
|
|
9147
|
+
const insertion = `${profile[0]}
|
|
9148
|
+
${profile[1]}"ui-components": () => import("@/pages/ui/UiComponentPage.vue"),`;
|
|
9149
|
+
return {
|
|
9150
|
+
content: source.slice(0, start) + block.replace(profile[0], insertion) + source.slice(end)
|
|
9151
|
+
};
|
|
9152
|
+
}
|
|
9153
|
+
function addComponentRouteBinding(source) {
|
|
9154
|
+
const unionStart = source.indexOf("export type PlatformAdminPageKey =");
|
|
9155
|
+
const interfaceStart = source.indexOf("export interface PlatformAdminRouteBinding", unionStart);
|
|
9156
|
+
if (unionStart < 0 || interfaceStart < 0) {
|
|
9157
|
+
return { error: "\u627E\u4E0D\u5230 PlatformAdminPageKey \u9879\u76EE\u951A\u70B9" };
|
|
9158
|
+
}
|
|
9159
|
+
let content = source;
|
|
9160
|
+
const union = content.slice(unionStart, interfaceStart);
|
|
9161
|
+
if (!union.includes('"ui-components"')) {
|
|
9162
|
+
const semicolon = content.lastIndexOf(";", interfaceStart);
|
|
9163
|
+
if (semicolon < unionStart) {
|
|
9164
|
+
return { error: "PlatformAdminPageKey \u7ED3\u675F\u951A\u70B9\u7F3A\u5931" };
|
|
9165
|
+
}
|
|
9166
|
+
content = `${content.slice(0, semicolon)}
|
|
9167
|
+
| "ui-components"${content.slice(semicolon)}`;
|
|
9168
|
+
}
|
|
9169
|
+
if (content.includes('routeKey: "system.ui-components"')) {
|
|
9170
|
+
return { content };
|
|
9171
|
+
}
|
|
9172
|
+
const features = [
|
|
9173
|
+
...content.matchAll(/^(\s*)\{ routeKey: "system\.feature", page: "resource", resourceKey: "features" \},$/gm)
|
|
9174
|
+
];
|
|
9175
|
+
if (features.length !== 1) {
|
|
9176
|
+
return { error: "system.feature Admin Route \u951A\u70B9\u4E0D\u552F\u4E00" };
|
|
9177
|
+
}
|
|
9178
|
+
const feature = features[0];
|
|
9179
|
+
return {
|
|
9180
|
+
content: content.replace(feature[0], `${feature[0]}
|
|
9181
|
+
${feature[1]}{ routeKey: "system.ui-components", page: "ui-components" },`)
|
|
9182
|
+
};
|
|
9183
|
+
}
|
|
9184
|
+
function preserve(input, reason) {
|
|
9185
|
+
return {
|
|
9186
|
+
path: input.path,
|
|
9187
|
+
ownership: "project-owned",
|
|
9188
|
+
action: "preserve",
|
|
9189
|
+
reason,
|
|
9190
|
+
expectedProjectContent: input.expected
|
|
9191
|
+
};
|
|
9192
|
+
}
|
|
9193
|
+
function update(input, content) {
|
|
9194
|
+
return {
|
|
9195
|
+
path: input.path,
|
|
9196
|
+
ownership: "project-owned",
|
|
9197
|
+
action: "update",
|
|
9198
|
+
reason: "\u7248\u672C recipe \u66F4\u65B0\u9879\u76EE Admin \u6269\u5C55\u951A\u70B9",
|
|
9199
|
+
expectedProjectContent: input.expected,
|
|
9200
|
+
content
|
|
9201
|
+
};
|
|
9202
|
+
}
|
|
9203
|
+
function conflict2(input, message) {
|
|
9204
|
+
return {
|
|
9205
|
+
path: input.path,
|
|
9206
|
+
ownership: "project-owned",
|
|
9207
|
+
action: "preserve",
|
|
9208
|
+
reason: "\u5BA2\u6237\u5DF2\u4FEE\u6539",
|
|
9209
|
+
expectedProjectContent: input.expected,
|
|
9210
|
+
conflict: message
|
|
9211
|
+
};
|
|
9212
|
+
}
|
|
9213
|
+
|
|
9091
9214
|
// src/update/planner-file.ts
|
|
9092
|
-
async function planFile(path2, baseRoot, targetRoot, source, target, ours, provenance, targetContentOverride, manageGeneratedProjectOwned = false, forcePlatformBaseline = false) {
|
|
9215
|
+
async function planFile(path2, baseRoot, targetRoot, source, target, ours, provenance, targetContentOverride, manageGeneratedProjectOwned = false, forcePlatformBaseline = false, manageRecipeProjectOwned = false) {
|
|
9093
9216
|
const [base, theirs] = await Promise.all([
|
|
9094
9217
|
readOptional(join23(baseRoot, path2)),
|
|
9095
9218
|
targetContentOverride ? Promise.resolve(targetContentOverride) : readOptional(join23(targetRoot, path2))
|
|
@@ -9097,6 +9220,9 @@ async function planFile(path2, baseRoot, targetRoot, source, target, ours, prove
|
|
|
9097
9220
|
const ownership = source?.ownership ?? target?.ownership ?? classifyOwnership(path2);
|
|
9098
9221
|
const expected = expectationForSnapshot(ours);
|
|
9099
9222
|
if (ownership === "project-owned") {
|
|
9223
|
+
if (manageRecipeProjectOwned) {
|
|
9224
|
+
return planRecipeProjectOwned({ path: path2, ours, expected });
|
|
9225
|
+
}
|
|
9100
9226
|
if (manageGeneratedProjectOwned) {
|
|
9101
9227
|
return planGeneratedProjectOwned(path2, source, target, ours, theirs, expected);
|
|
9102
9228
|
}
|
|
@@ -9104,26 +9230,26 @@ async function planFile(path2, baseRoot, targetRoot, source, target, ours, prove
|
|
|
9104
9230
|
}
|
|
9105
9231
|
if (path2.startsWith("packages/database/drizzle/") && path2.endsWith(".sql")) {
|
|
9106
9232
|
if (source && !target) {
|
|
9107
|
-
return
|
|
9233
|
+
return conflict3(path2, ownership, "\u5DF2\u53D1\u5E03 migration \u4E0D\u53EF\u5220\u9664", expected);
|
|
9108
9234
|
}
|
|
9109
9235
|
if (source && target && source.hash !== target.hash) {
|
|
9110
|
-
return
|
|
9236
|
+
return conflict3(path2, ownership, "\u5DF2\u53D1\u5E03 migration \u4E0D\u53EF\u6539\u5199\uFF0C\u53EA\u80FD\u8FFD\u52A0\u65B0\u6587\u4EF6", expected);
|
|
9111
9237
|
}
|
|
9112
9238
|
}
|
|
9113
9239
|
if (!source && target) {
|
|
9114
9240
|
if (!ours) {
|
|
9115
9241
|
return readyPlannedFile(path2, ownership, "create", "\u76EE\u6807\u7248\u672C\u65B0\u589E", expected, theirs);
|
|
9116
9242
|
}
|
|
9117
|
-
return theirs && hashContent(ours) === target.hash ? readyPlannedFile(path2, ownership, "preserve", "\u5DF2\u5305\u542B\u76EE\u6807\u5185\u5BB9", expected) :
|
|
9243
|
+
return theirs && hashContent(ours) === target.hash ? readyPlannedFile(path2, ownership, "preserve", "\u5DF2\u5305\u542B\u76EE\u6807\u5185\u5BB9", expected) : conflict3(path2, ownership, "\u76EE\u6807\u7248\u672C\u65B0\u589E\u8DEF\u5F84\u5DF2\u88AB\u9879\u76EE\u5360\u7528", expected);
|
|
9118
9244
|
}
|
|
9119
9245
|
if (source && !target) {
|
|
9120
9246
|
if (!ours) {
|
|
9121
9247
|
return readyPlannedFile(path2, ownership, "preserve", "\u9879\u76EE\u5DF2\u5220\u9664", expected);
|
|
9122
9248
|
}
|
|
9123
|
-
return hashContent(ours) === source.hash ? readyPlannedFile(path2, ownership, "delete", "\u76EE\u6807\u7248\u672C\u5220\u9664", expected) :
|
|
9249
|
+
return hashContent(ours) === source.hash ? readyPlannedFile(path2, ownership, "delete", "\u76EE\u6807\u7248\u672C\u5220\u9664", expected) : conflict3(path2, ownership, "\u5BA2\u6237\u4FEE\u6539\u540E Windy \u53C8\u5220\u9664", expected);
|
|
9124
9250
|
}
|
|
9125
9251
|
if (!source || !target || !ours || !theirs) {
|
|
9126
|
-
return
|
|
9252
|
+
return conflict3(path2, ownership, "\u6587\u4EF6\u72B6\u6001\u4E0D\u5B8C\u6574\uFF0C\u4E0D\u80FD\u5B89\u5168\u63A8\u65AD", expected);
|
|
9127
9253
|
}
|
|
9128
9254
|
const oursChanged = hashContent(ours) !== source.hash;
|
|
9129
9255
|
const sourceArtifactHash = artifactHashOf(source);
|
|
@@ -9137,7 +9263,7 @@ async function planFile(path2, baseRoot, targetRoot, source, target, ours, prove
|
|
|
9137
9263
|
});
|
|
9138
9264
|
if (forceTargetBaseline && path2.endsWith("package.json") && base) {
|
|
9139
9265
|
const merged = mergePackageJson(base, ours, theirs);
|
|
9140
|
-
return merged.content ? readyPlannedFile(path2, ownership, "update", "package.json \u5B57\u6BB5\u7EA7\u5408\u5E76", expected, merged.content) :
|
|
9266
|
+
return merged.content ? readyPlannedFile(path2, ownership, "update", "package.json \u5B57\u6BB5\u7EA7\u5408\u5E76", expected, merged.content) : conflict3(path2, ownership, `\u5B57\u6BB5\u51B2\u7A81\uFF1A${merged.conflicts.join("\u3001")}`, expected);
|
|
9141
9267
|
}
|
|
9142
9268
|
if (forceTargetBaseline) {
|
|
9143
9269
|
return readyPlannedFile(path2, ownership, "update", "\u5F3A\u5236\u91C7\u7528\u76EE\u6807\u5E73\u53F0\u57FA\u7EBF", expected, theirs);
|
|
@@ -9147,11 +9273,11 @@ async function planFile(path2, baseRoot, targetRoot, source, target, ours, prove
|
|
|
9147
9273
|
}
|
|
9148
9274
|
if (targetChanged && path2.endsWith("package.json") && base) {
|
|
9149
9275
|
const merged = mergePackageJson(base, ours, theirs);
|
|
9150
|
-
return merged.content ? readyPlannedFile(path2, ownership, "update", "package.json \u5B57\u6BB5\u7EA7\u5408\u5E76", expected, merged.content) :
|
|
9276
|
+
return merged.content ? readyPlannedFile(path2, ownership, "update", "package.json \u5B57\u6BB5\u7EA7\u5408\u5E76", expected, merged.content) : conflict3(path2, ownership, `\u5B57\u6BB5\u51B2\u7A81\uFF1A${merged.conflicts.join("\u3001")}`, expected);
|
|
9151
9277
|
}
|
|
9152
9278
|
if (!oursChanged && targetChanged && adoptedProjectDelta && base) {
|
|
9153
9279
|
const merged = await mergeTextFile(base, ours, theirs);
|
|
9154
|
-
return merged.content ? readyPlannedFile(path2, ownership, "update", "\u4E09\u65B9\u5408\u5E76\u5DF2\u91C7\u7EB3\u9879\u76EE\u589E\u91CF", expected, merged.content) :
|
|
9280
|
+
return merged.content ? readyPlannedFile(path2, ownership, "update", "\u4E09\u65B9\u5408\u5E76\u5DF2\u91C7\u7EB3\u9879\u76EE\u589E\u91CF", expected, merged.content) : conflict3(path2, ownership, merged.conflict || "\u4E09\u65B9\u5408\u5E76\u5931\u8D25", expected);
|
|
9155
9281
|
}
|
|
9156
9282
|
if (!oursChanged) {
|
|
9157
9283
|
return readyPlannedFile(path2, ownership, "update", "\u4EC5 Windy \u4FEE\u6539", expected, theirs);
|
|
@@ -9164,9 +9290,9 @@ async function planFile(path2, baseRoot, targetRoot, source, target, ours, prove
|
|
|
9164
9290
|
}
|
|
9165
9291
|
if (base) {
|
|
9166
9292
|
const merged = await mergeTextFile(base, ours, theirs);
|
|
9167
|
-
return merged.content ? readyPlannedFile(path2, ownership, "update", "\u4E09\u65B9\u5408\u5E76", expected, merged.content) :
|
|
9293
|
+
return merged.content ? readyPlannedFile(path2, ownership, "update", "\u4E09\u65B9\u5408\u5E76", expected, merged.content) : conflict3(path2, ownership, merged.conflict || "\u4E09\u65B9\u5408\u5E76\u5931\u8D25", expected);
|
|
9168
9294
|
}
|
|
9169
|
-
return
|
|
9295
|
+
return conflict3(path2, ownership, "\u5BA2\u6237\u548C Windy \u5747\u5DF2\u4FEE\u6539", expected);
|
|
9170
9296
|
}
|
|
9171
9297
|
function shouldForcePlatformBaseline(input) {
|
|
9172
9298
|
return input.forcePlatformBaseline && !input.oursChanged && input.base !== undefined && hashContent(input.base) !== input.sourceArtifactHash;
|
|
@@ -9187,7 +9313,7 @@ function readyPlannedFile(path2, ownership, action, reason, expectedProjectConte
|
|
|
9187
9313
|
function expectationForSnapshot(content) {
|
|
9188
9314
|
return content ? { kind: "hash", value: hashContent(content) } : { kind: "missing" };
|
|
9189
9315
|
}
|
|
9190
|
-
function
|
|
9316
|
+
function conflict3(path2, ownership, reason, expectedProjectContent) {
|
|
9191
9317
|
return {
|
|
9192
9318
|
path: path2,
|
|
9193
9319
|
ownership,
|
|
@@ -9361,6 +9487,7 @@ async function createUpdatePlan(request, artifacts, dependencies = {}) {
|
|
|
9361
9487
|
const versionRecipes = (dependencies.resolveVersionRecipes ?? resolveRecipeChain)(provenance.windyVersion, targetVersion);
|
|
9362
9488
|
const moduleRecipes = resolveModuleRecipeIds(request);
|
|
9363
9489
|
const recipes2 = [...versionRecipes.map(({ id }) => id), ...moduleRecipes];
|
|
9490
|
+
const recipeProjectOwnedPaths = recipeManagedProjectOwnedPaths(recipes2);
|
|
9364
9491
|
const [rawSourceManifest, rawTargetManifest] = await Promise.all([
|
|
9365
9492
|
readManagedFilesManifest(request.projectDirectory),
|
|
9366
9493
|
readManagedFilesManifest(artifactSet.targetDirectory)
|
|
@@ -9407,7 +9534,8 @@ async function createUpdatePlan(request, artifacts, dependencies = {}) {
|
|
|
9407
9534
|
artifactSet,
|
|
9408
9535
|
provenance,
|
|
9409
9536
|
manageGeneratedProjectOwned: request.targetModules !== undefined,
|
|
9410
|
-
forcePlatformBaseline: request.forcePlatformBaseline === true
|
|
9537
|
+
forcePlatformBaseline: request.forcePlatformBaseline === true,
|
|
9538
|
+
recipeProjectOwnedPaths
|
|
9411
9539
|
});
|
|
9412
9540
|
preserveTestsForExistingProjectOwnedSources(files, sourceEntries);
|
|
9413
9541
|
await deriveDockerfilePlans(files, projectSnapshots, new Set([
|
|
@@ -9486,7 +9614,7 @@ async function planFiles(input) {
|
|
|
9486
9614
|
} else if (forcedContent) {
|
|
9487
9615
|
files.push(readyPlannedFile(path2, input.targetEntries.get(path2)?.ownership ?? input.sourceEntries.get(path2)?.ownership ?? classifyOwnership(path2), "update", "\u5408\u5E76\u5E73\u53F0\u4E0E\u4E0B\u6E38 migration \u5386\u53F2", expected, forcedContent));
|
|
9488
9616
|
} else {
|
|
9489
|
-
files.push(await planFile(path2, input.artifactSet.baseDirectory, input.artifactSet.targetDirectory, input.sourceEntries.get(path2), input.targetEntries.get(path2), ours, input.provenance, input.migrations.contentOverrides.get(path2), input.manageGeneratedProjectOwned, input.forcePlatformBaseline));
|
|
9617
|
+
files.push(await planFile(path2, input.artifactSet.baseDirectory, input.artifactSet.targetDirectory, input.sourceEntries.get(path2), input.targetEntries.get(path2), ours, input.provenance, input.migrations.contentOverrides.get(path2), input.manageGeneratedProjectOwned, input.forcePlatformBaseline, input.recipeProjectOwnedPaths.has(path2)));
|
|
9490
9618
|
}
|
|
9491
9619
|
}
|
|
9492
9620
|
return files;
|
|
@@ -10007,9 +10135,9 @@ function assertEnabledConflicts(target, catalog) {
|
|
|
10007
10135
|
for (const [name, enabled] of target) {
|
|
10008
10136
|
if (!enabled)
|
|
10009
10137
|
continue;
|
|
10010
|
-
const
|
|
10011
|
-
if (
|
|
10012
|
-
throw new Error(`\u6A21\u5757\u51B2\u7A81\uFF1A${name} \u4E0E ${
|
|
10138
|
+
const conflict4 = catalog.get(name).conflicts.find((candidate) => target.get(candidate));
|
|
10139
|
+
if (conflict4)
|
|
10140
|
+
throw new Error(`\u6A21\u5757\u51B2\u7A81\uFF1A${name} \u4E0E ${conflict4}`);
|
|
10013
10141
|
}
|
|
10014
10142
|
}
|
|
10015
10143
|
function assertCompatible(version, range, name) {
|
|
@@ -10024,7 +10152,7 @@ function reconcileProjectModuleRegistryPlan(plan, current, lifecycle) {
|
|
|
10024
10152
|
if (!registryFile) {
|
|
10025
10153
|
throw new Error("\u6A21\u5757\u751F\u547D\u5468\u671F\u8BA1\u5212\u7F3A\u5C11 project-owned \u88C5\u914D\u9009\u62E9");
|
|
10026
10154
|
}
|
|
10027
|
-
plan.conflicts = plan.conflicts.filter((
|
|
10155
|
+
plan.conflicts = plan.conflicts.filter((conflict4) => !conflict4.startsWith(`${projectModuleRegistryPath}\uFF1A`));
|
|
10028
10156
|
delete registryFile.conflict;
|
|
10029
10157
|
if (lifecycle.action === "update") {
|
|
10030
10158
|
registryFile.action = "preserve";
|
|
@@ -10454,8 +10582,8 @@ function printModulePlan(plan, log) {
|
|
|
10454
10582
|
for (const file of changed) {
|
|
10455
10583
|
log(` ${file.action.padEnd(6)} ${file.path}\uFF08${file.reason}\uFF09`);
|
|
10456
10584
|
}
|
|
10457
|
-
for (const
|
|
10458
|
-
log(` conflict ${
|
|
10585
|
+
for (const conflict4 of plan.conflicts)
|
|
10586
|
+
log(` conflict ${conflict4}`);
|
|
10459
10587
|
}
|
|
10460
10588
|
function hasChanges(lifecycle) {
|
|
10461
10589
|
return lifecycle.addedModules.length > 0 || lifecycle.updatedModules.length > 0 || lifecycle.removedModules.length > 0;
|
|
@@ -10627,8 +10755,8 @@ function printPlan(plan) {
|
|
|
10627
10755
|
console.log(`\u8BA1\u5212\u6539\u52A8\uFF1A${changed.length} \u4E2A\u6587\u4EF6\uFF1B\u51B2\u7A81\uFF1A${plan.conflicts.length}`);
|
|
10628
10756
|
for (const file of changed)
|
|
10629
10757
|
console.log(` ${file.action.padEnd(6)} ${file.path}\uFF08${file.reason}\uFF09`);
|
|
10630
|
-
for (const
|
|
10631
|
-
console.log(` conflict ${
|
|
10758
|
+
for (const conflict4 of plan.conflicts)
|
|
10759
|
+
console.log(` conflict ${conflict4}`);
|
|
10632
10760
|
if (plan.detachedToolchain)
|
|
10633
10761
|
console.log("Oxc\uFF1A\u68C0\u6D4B\u5230\u5BA2\u6237\u63A5\u7BA1\uFF0C\u76F8\u5173\u914D\u7F6E\u4FDD\u6301\u4E0D\u53D8");
|
|
10634
10762
|
}
|
package/package.json
CHANGED
package/template/README.md
CHANGED
|
@@ -132,7 +132,8 @@ browser-safe `./admin` 与 project-owned 的
|
|
|
132
132
|
`apps/web/src/app/web-composition.ts` 装配页面,不复制第二份 Feature、Permission
|
|
133
133
|
或路由清单。整个 `apps/web/src` 与整个 `apps/server/src/app` 都是
|
|
134
134
|
project-owned,客户可以修改 Router、layout、页面、Host 和 Server 组合;普通
|
|
135
|
-
`windy update`
|
|
135
|
+
`windy update` 不会整文件覆盖或三方合并这些文件。版本 recipe 只有在显式登记
|
|
136
|
+
path 与确定性语义锚点后才能做幂等追加;锚点缺失或不唯一时必须在写入前停止。
|
|
136
137
|
|
|
137
138
|
精确生成来源保留在 `.windy/generation.json`。当前版本已提供可回滚的
|
|
138
139
|
`windy update` 与 `windy module list/enable/disable`。当前内置模块仍依赖
|
|
@@ -166,7 +167,8 @@ bun run format
|
|
|
166
167
|
更新前无需提交、暂存或清理业务改动。`--dry-run` 只生成计划,不写项目;无关 WIP
|
|
167
168
|
保持原样,命中同一路径时按冻结快照与三方合并 fail closed。正式更新先备份,再做
|
|
168
169
|
package 字段级合并、共享脚手架三方合并与完整验证。整个 `apps/web/src` 和
|
|
169
|
-
`apps/server/src/app`
|
|
170
|
+
`apps/server/src/app` 默认保持项目版本;已登记版本 recipe 只修改其声明的窄锚点。
|
|
171
|
+
任何冲突都会在写入前中止,模块启停在交互
|
|
170
172
|
终端确认;CI、Agent 或其它非 TTY 环境必须显式传入 `--yes`。
|
|
171
173
|
无并发变化的验证失败会自动回滚;中断恢复使用
|
|
172
174
|
`bun run windy doctor --repair`。该命令在提交点前保留并发编辑并回滚其余路径,
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "vitest";
|
|
2
|
+
import { mount, type VueWrapper } from "@vue/test-utils";
|
|
3
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
4
|
+
import { buildComponentCatalog, componentCatalog } from "./component-catalog";
|
|
5
|
+
import UiComponentGallery from "./components/UiComponentGallery.vue";
|
|
6
|
+
|
|
7
|
+
let wrapper: VueWrapper | undefined;
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
wrapper?.unmount();
|
|
11
|
+
wrapper = undefined;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe("组件总览页", () => {
|
|
15
|
+
test("组件清单与 components/ui 实际目录保持一致", () => {
|
|
16
|
+
const componentDirectories = Object.keys(
|
|
17
|
+
import.meta.glob("../../components/ui/*/index.ts"),
|
|
18
|
+
)
|
|
19
|
+
.map((path) => path.split("/").at(-2))
|
|
20
|
+
.filter((name): name is string => Boolean(name))
|
|
21
|
+
.sort();
|
|
22
|
+
|
|
23
|
+
expect(componentCatalog.map(({ id }) => id).sort()).toEqual(
|
|
24
|
+
componentDirectories,
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("展示 components/ui 扫描到的全部组件族", () => {
|
|
29
|
+
wrapper = mountGallery();
|
|
30
|
+
|
|
31
|
+
expect(wrapper.findAll("[data-component-id]")).toHaveLength(
|
|
32
|
+
componentCatalog.length,
|
|
33
|
+
);
|
|
34
|
+
expect(
|
|
35
|
+
wrapper
|
|
36
|
+
.findAll("[data-component-id]")
|
|
37
|
+
.map((node) => node.attributes("data-component-id")),
|
|
38
|
+
).toEqual(componentCatalog.map(({ id }) => id));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("自动登记下游项目新增的组件入口与具名导出", () => {
|
|
42
|
+
const catalog = buildComponentCatalog({
|
|
43
|
+
"../../components/ui/button/index.ts": { Button: {} },
|
|
44
|
+
"../../components/ui/radio-group/index.ts": {
|
|
45
|
+
RadioGroup: {},
|
|
46
|
+
RadioGroupItem: {},
|
|
47
|
+
},
|
|
48
|
+
"../../components/ui/textarea/index.ts": { Textarea: {} },
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(
|
|
52
|
+
catalog.map(({ id, category, exports }) => ({
|
|
53
|
+
id,
|
|
54
|
+
category,
|
|
55
|
+
exports,
|
|
56
|
+
})),
|
|
57
|
+
).toEqual([
|
|
58
|
+
{ id: "button", category: "foundation", exports: ["Button"] },
|
|
59
|
+
{
|
|
60
|
+
id: "radio-group",
|
|
61
|
+
category: "project",
|
|
62
|
+
exports: ["RadioGroup", "RadioGroupItem"],
|
|
63
|
+
},
|
|
64
|
+
{ id: "textarea", category: "project", exports: ["Textarea"] },
|
|
65
|
+
]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("支持按关键词和分类筛选", async () => {
|
|
69
|
+
wrapper = mountGallery();
|
|
70
|
+
|
|
71
|
+
await wrapper.get('input[aria-label="搜索组件"]').setValue("消息通知");
|
|
72
|
+
expect(wrapper.findAll("[data-component-id]")).toHaveLength(1);
|
|
73
|
+
expect(
|
|
74
|
+
wrapper.get("[data-component-id]").attributes("data-component-id"),
|
|
75
|
+
).toBe("sonner");
|
|
76
|
+
|
|
77
|
+
await wrapper.get('input[aria-label="搜索组件"]').setValue("");
|
|
78
|
+
await wrapper.get('[data-category-id="foundation"]').trigger("click");
|
|
79
|
+
expect(wrapper.findAll("[data-component-id]")).toHaveLength(7);
|
|
80
|
+
expect(wrapper.text()).toContain(
|
|
81
|
+
`当前显示 7 / ${componentCatalog.length} 个组件族`,
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
function mountGallery() {
|
|
87
|
+
return mount(
|
|
88
|
+
{
|
|
89
|
+
components: { TooltipProvider, UiComponentGallery },
|
|
90
|
+
template: "<TooltipProvider><UiComponentGallery /></TooltipProvider>",
|
|
91
|
+
},
|
|
92
|
+
{ attachTo: document.body },
|
|
93
|
+
);
|
|
94
|
+
}
|