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.
Files changed (30) hide show
  1. package/dist/cli.js +152 -24
  2. package/package.json +1 -1
  3. package/template/.windy-template.json +2 -2
  4. package/template/README.md +4 -2
  5. package/template/apps/server/src/runtime-development.ts +1 -0
  6. package/template/apps/web/src/layout/AppSidebar.vue +1 -0
  7. package/template/apps/web/src/pages/ui/UiComponentPage.vue +7 -0
  8. package/template/apps/web/src/pages/ui/UiComponentPage.webtest.ts +94 -0
  9. package/template/apps/web/src/pages/ui/component-catalog.ts +248 -0
  10. package/template/apps/web/src/pages/ui/components/ComponentPreviewCard.vue +50 -0
  11. package/template/apps/web/src/pages/ui/components/DataComponentShowcases.vue +131 -0
  12. package/template/apps/web/src/pages/ui/components/FeedbackComponentShowcases.vue +158 -0
  13. package/template/apps/web/src/pages/ui/components/FoundationComponentShowcases.vue +129 -0
  14. package/template/apps/web/src/pages/ui/components/NavigationComponentShowcases.vue +155 -0
  15. package/template/apps/web/src/pages/ui/components/ProjectComponentShowcases.vue +31 -0
  16. package/template/apps/web/src/pages/ui/components/UiComponentGallery.vue +238 -0
  17. package/template/apps/web/src/router/index.ts +2 -0
  18. package/template/apps/web/src/router/manifest-routes.webtest.ts +1 -0
  19. package/template/apps/web/src/router/platform-admin-route-bindings.ts +3 -1
  20. package/template/docs/development/business-web-routes.md +3 -1
  21. package/template/docs/development/ui-component-gallery.md +44 -0
  22. package/template/docs/operations/incremental-delivery.md +10 -9
  23. package/template/packages/modules/package.json +1 -1
  24. package/template/packages/modules/src/system-admin-routes.ts +8 -0
  25. package/template/packages/modules/src/system-admin-ui-catalog.ts +1 -1
  26. package/template/packages/modules/src/system-features.ts +5 -0
  27. package/template/packages/modules/src/system-module-catalog.ts +2 -1
  28. package/template/packages/modules/src/system-modules.ts +11 -0
  29. package/template/packages/modules/src/system-permissions.ts +2 -0
  30. 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\u3002\u4EFB\u4F55\u51B2\u7A81\u90FD\u4F1A\u5728\u5199\u5165\u524D\u4E2D\u6B62\uFF0C\u6A21\u5757\u542F\u505C\u5728\u4EA4\u4E92
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 conflict2(path2, ownership, "\u5DF2\u53D1\u5E03 migration \u4E0D\u53EF\u5220\u9664", expected);
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 conflict2(path2, ownership, "\u5DF2\u53D1\u5E03 migration \u4E0D\u53EF\u6539\u5199\uFF0C\u53EA\u80FD\u8FFD\u52A0\u65B0\u6587\u4EF6", expected);
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) : conflict2(path2, ownership, "\u76EE\u6807\u7248\u672C\u65B0\u589E\u8DEF\u5F84\u5DF2\u88AB\u9879\u76EE\u5360\u7528", 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) : conflict2(path2, ownership, "\u5BA2\u6237\u4FEE\u6539\u540E Windy \u53C8\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 conflict2(path2, ownership, "\u6587\u4EF6\u72B6\u6001\u4E0D\u5B8C\u6574\uFF0C\u4E0D\u80FD\u5B89\u5168\u63A8\u65AD", expected);
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) : conflict2(path2, ownership, `\u5B57\u6BB5\u51B2\u7A81\uFF1A${merged.conflicts.join("\u3001")}`, expected);
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) : conflict2(path2, ownership, `\u5B57\u6BB5\u51B2\u7A81\uFF1A${merged.conflicts.join("\u3001")}`, expected);
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) : conflict2(path2, ownership, merged.conflict || "\u4E09\u65B9\u5408\u5E76\u5931\u8D25", expected);
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) : conflict2(path2, ownership, merged.conflict || "\u4E09\u65B9\u5408\u5E76\u5931\u8D25", expected);
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 conflict2(path2, ownership, "\u5BA2\u6237\u548C Windy \u5747\u5DF2\u4FEE\u6539", expected);
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 conflict2(path2, ownership, reason, expectedProjectContent) {
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 conflict3 = catalog.get(name).conflicts.find((candidate) => target.get(candidate));
10011
- if (conflict3)
10012
- throw new Error(`\u6A21\u5757\u51B2\u7A81\uFF1A${name} \u4E0E ${conflict3}`);
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((conflict3) => !conflict3.startsWith(`${projectModuleRegistryPath}\uFF1A`));
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 conflict3 of plan.conflicts)
10458
- log(` conflict ${conflict3}`);
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 conflict3 of plan.conflicts)
10631
- console.log(` conflict ${conflict3}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-windy",
3
- "version": "0.6.8",
3
+ "version": "0.6.9",
4
4
  "description": "创建单组织、单租户、私有部署优先的 Windy 企业 Dashboard Starter",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "templateCommit": "47527e1bb3a2848bbb7d27eef7132c83b81d714d",
4
- "generatorVersion": "0.6.8",
3
+ "templateCommit": "9176c4dc2db60979b5ce7fd89639da90648c49b9",
4
+ "generatorVersion": "0.6.9",
5
5
  "modules": [
6
6
  {
7
7
  "name": "system",
@@ -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`。该命令在提交点前保留并发编辑并回滚其余路径,
@@ -36,6 +36,7 @@ export function developmentAdminActor(): RequestActor {
36
36
  "system.dict.manage",
37
37
  "system.feature.read",
38
38
  "system.feature.manage",
39
+ "system.ui-components.read",
39
40
  "system.search.read",
40
41
  "system.search.manage",
41
42
  "system.settings.read",
@@ -52,6 +52,7 @@ interface NavigationGroup {
52
52
  const iconMap: Record<string, Component> = {
53
53
  activity: ActivityIcon,
54
54
  bell: BellIcon,
55
+ blocks: BlocksIcon,
55
56
  "layout-dashboard": LayoutDashboardIcon,
56
57
  "book-open": BookOpenIcon,
57
58
  "building-2": Building2Icon,
@@ -0,0 +1,7 @@
1
+ <script setup lang="ts">
2
+ import UiComponentGallery from "./components/UiComponentGallery.vue";
3
+ </script>
4
+
5
+ <template>
6
+ <UiComponentGallery />
7
+ </template>
@@ -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
+ }