create-windy 0.2.29 → 0.2.30

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 CHANGED
@@ -394,8 +394,9 @@ var optionalModuleManifests = [
394
394
  "apps/server/src/example-permissions.ts"
395
395
  ],
396
396
  documentation: ["docs/development/example-work-order.md"],
397
- schemaFiles: ["packages/database/src/schema/work-orders.ts"],
398
- databaseTables: ["work_orders"]
397
+ schemaFiles: ["packages/example-work-order/src/drizzle-schema.ts"],
398
+ databaseTables: ["work_orders"],
399
+ databaseLineageOwner: "module"
399
400
  }
400
401
  ];
401
402
  function module(name, label, description, dependencies) {
@@ -414,6 +415,7 @@ function module(name, label, description, dependencies) {
414
415
  documentation: [],
415
416
  schemaFiles: [],
416
417
  databaseTables: [],
418
+ databaseLineageOwner: "platform",
417
419
  transientDatabaseTables: [],
418
420
  databaseStatementTokens: [],
419
421
  uninstallPolicy: "retain-data"
@@ -628,6 +630,7 @@ function module2(name, label, description, selection, dependencies = []) {
628
630
  documentation: [],
629
631
  schemaFiles: [],
630
632
  databaseTables: [],
633
+ databaseLineageOwner: "platform",
631
634
  transientDatabaseTables: [],
632
635
  databaseStatementTokens: [],
633
636
  uninstallPolicy: "retain-data"
@@ -735,6 +738,7 @@ var helpText = `create-windy <项目名> [选项]
735
738
  create-windy update --check [--cwd <目录>]
736
739
  create-windy update --dry-run [--to <版本>] [--cwd <目录>]
737
740
  create-windy update [--to <版本>] [--cwd <目录>]
741
+ create-windy schema handoff [--config <JSON>] [--cwd <目录>]
738
742
 
739
743
  选项:
740
744
  --profile private-enterprise 单组织、单租户、私有部署 profile
@@ -1200,8 +1204,11 @@ var ignoredFiles = new Set([
1200
1204
  ".windy/update-state.json"
1201
1205
  ]);
1202
1206
  var projectOwnedRoots = [
1207
+ "module-schema.lineages.json",
1208
+ "module-schema",
1203
1209
  "apps/web/src/app",
1204
1210
  "apps/server/src/installed-business-modules.ts",
1211
+ "apps/server/src/domain-schema",
1205
1212
  "apps/server/src/work-order",
1206
1213
  "packages/example-work-order"
1207
1214
  ];
@@ -1320,10 +1327,13 @@ async function materializeSelectedModules(targetDirectory, selectedNames) {
1320
1327
  removedPackageNames.add(dependency.name);
1321
1328
  }
1322
1329
  await stripModuleBlocks(targetDirectory, selected);
1323
- await pruneDatabaseArtifacts(targetDirectory, removed.flatMap(({ databaseTables, transientDatabaseTables }) => [
1324
- ...databaseTables,
1325
- ...transientDatabaseTables
1326
- ]), removed.flatMap(({ databaseStatementTokens }) => databaseStatementTokens));
1330
+ await pruneDatabaseArtifacts(targetDirectory, [
1331
+ ...removed.flatMap(({ databaseTables, transientDatabaseTables }) => [
1332
+ ...databaseTables,
1333
+ ...transientDatabaseTables
1334
+ ]),
1335
+ ...moduleDistributionCatalog.filter(({ databaseLineageOwner }) => databaseLineageOwner === "module").flatMap(({ databaseTables }) => databaseTables)
1336
+ ], removed.flatMap(({ databaseStatementTokens }) => databaseStatementTokens));
1327
1337
  const removedRoots = removed.flatMap((manifest) => [
1328
1338
  ...manifest.packages,
1329
1339
  ...manifest.fileRoots,
@@ -1624,6 +1634,7 @@ License 签发中心、独立签名服务、Vault、发布治理、内部任务
1624
1634
  规划属于软件供应方控制面,不进入客户 Starter。
1625
1635
 
1626
1636
  - 架构:从 [Module Manifest 组合](./architecture/module-manifest-composition.md) 开始。
1637
+ - 数据库:业务表使用 [Migration Bundle 与独立 Drizzle lineage](./architecture/migration-bundle.md)。
1627
1638
  - 平台开发:从 [系统 CRUD API](./platform/system-crud-api.md) 和 [CRUD 生成器](./development/crud-generator.md) 开始。
1628
1639
  ${includeOfflineLicense ? "- License 消费端:[离线激活与安装协议](./license/license-offline-activation-code.md);运行时能力由 npm 依赖 `@southwind-ai/license` 提供。" : ""}
1629
1640
  - 运维:[可观测性](./operations/observability.md) 与 [运维总览](./platform/operations-overview.md)。
@@ -1748,6 +1759,9 @@ create-windy 在本机 \`.env\` 中随机生成的 \`WINDY_BOOTSTRAP_ADMIN_PASSW
1748
1759
  | ---------- | -------- | ------------ | ---- |
1749
1760
  ${moduleRows}
1750
1761
 
1762
+ 项目自有业务模块以 \`apps/server/src/installed-business-modules.ts\` 装配的 Module Manifest
1763
+ 为准;shared-scaffold README 不复制项目业务 Key,避免平台升级固化产品命名。
1764
+
1751
1765
  精确生成来源和选择记录在 \`.windy/generation.json\`。当前版本已提供可回滚的
1752
1766
  \`windy update\`;通用 \`module add/remove\` 尚未交付,不要把 Feature Flag 关闭当成
1753
1767
  模块已经卸载。
@@ -1876,6 +1890,7 @@ var templateRootFiles = [
1876
1890
  "bunfig.toml",
1877
1891
  "docker-compose.yml",
1878
1892
  "drizzle.config.ts",
1893
+ "module-schema.lineages.json",
1879
1894
  "package.json"
1880
1895
  ];
1881
1896
  var templateDirectories = [
@@ -1902,6 +1917,7 @@ var templateDocumentationFiles = [
1902
1917
  "docs/architecture/data-scope-query-enforcement.md",
1903
1918
  "docs/architecture/durable-jobs.md",
1904
1919
  "docs/architecture/minimal-approval-workflow.md",
1920
+ "docs/architecture/migration-bundle.md",
1905
1921
  "docs/architecture/module-manifest-composition.md",
1906
1922
  "docs/architecture/object-storage.md",
1907
1923
  "docs/architecture/search-provider-protocol.md",
@@ -4338,7 +4354,8 @@ var recipes = [
4338
4354
  { id: "starter-0.2.25-to-0.2.26", from: "0.2.25", to: "0.2.26" },
4339
4355
  { id: "starter-0.2.26-to-0.2.27", from: "0.2.26", to: "0.2.27" },
4340
4356
  { id: "starter-0.2.27-to-0.2.28", from: "0.2.27", to: "0.2.28" },
4341
- { id: "starter-0.2.28-to-0.2.29", from: "0.2.28", to: "0.2.29" }
4357
+ { id: "starter-0.2.28-to-0.2.29", from: "0.2.28", to: "0.2.29" },
4358
+ { id: "starter-0.2.29-to-0.2.30", from: "0.2.29", to: "0.2.30" }
4342
4359
  ];
4343
4360
  function resolveRecipeChain(sourceVersion, targetVersion) {
4344
4361
  if (sourceVersion === targetVersion)
@@ -4466,11 +4483,32 @@ var known023DownstreamHashes = new Map([
4466
4483
  "81fb08f26e5baa3238606d62b9c46761a68c90f3967ea639b178c80c74c69fc1"
4467
4484
  ]
4468
4485
  ]);
4486
+ var known029DownstreamHashes = new Map([
4487
+ [
4488
+ "README.md",
4489
+ "a8b10907e151c28768678c016d102f98c3414981a65a314d75b2049fb1f9fe35"
4490
+ ],
4491
+ [
4492
+ "drizzle.config.ts",
4493
+ "c765b611d68b3a42375ea3530048fc7f32b1c7161257fa16f9dd093b580963e3"
4494
+ ],
4495
+ [
4496
+ "packages/database/src/schema/index.ts",
4497
+ "250682d5182b580a9e1fae27e7a596e6a503b41d6f5dca9e4322f5c6094d0976"
4498
+ ],
4499
+ [
4500
+ "apps/web/src/router/shared-scaffold-neutrality.webtest.ts",
4501
+ "420f1093eefcccf2a321e12c403628854c9a4a0b459ee14e96883557cd71dcaa"
4502
+ ]
4503
+ ]);
4469
4504
  var wrongExampleDescription = "创建时包含了 `work-order` 示例模块,用于演示 Module Manifest 接入;它不是生产工单产品,可以在正式开发前替换。";
4470
4505
  var correctCoreDescription = "创建时未选择 `work-order` 示例模块,当前项目只包含平台模块。";
4471
4506
  var wrongExampleRow = /^\|\s*示例工单\s*\|\s*已包含\s*\|$/m;
4472
4507
  var correctCoreRow = /^\|\s*示例工单\s*\|\s*未包含\s*\|$/m;
4473
4508
  function repairKnownDerivedFile(input) {
4509
+ const ownershipRepair = repairKnown029Downstream(input);
4510
+ if (ownershipRepair !== undefined)
4511
+ return ownershipRepair;
4474
4512
  const neutralRouterRepair = repairKnown026Downstream(input);
4475
4513
  if (neutralRouterRepair !== undefined)
4476
4514
  return neutralRouterRepair;
@@ -4494,6 +4532,46 @@ function repairKnownDerivedFile(input) {
4494
4532
  }
4495
4533
  return input.content.replace(wrongExampleDescription, correctCoreDescription).replace(wrongExampleRow, targetRow);
4496
4534
  }
4535
+ function repairKnown029Downstream(input) {
4536
+ if (input.sourceVersion !== "0.2.29" || known029DownstreamHashes.get(input.path) !== contentHash(input.content)) {
4537
+ return;
4538
+ }
4539
+ if (input.path === "drizzle.config.ts" || input.path === "apps/web/src/router/shared-scaffold-neutrality.webtest.ts") {
4540
+ return input.targetContent;
4541
+ }
4542
+ if (input.path === "packages/database/src/schema/index.ts") {
4543
+ return removeUnknownSchemaExports(input.content, input.targetContent);
4544
+ }
4545
+ if (input.path === "README.md") {
4546
+ return removeProjectModuleRows(input.content, input.targetContent);
4547
+ }
4548
+ }
4549
+ function removeUnknownSchemaExports(content, targetContent) {
4550
+ const targetExports = new Set(targetContent.split(`
4551
+ `).filter((line) => line.startsWith("export ") && line.includes(" from ")));
4552
+ return content.split(`
4553
+ `).filter((line) => !line.startsWith("export ") || !line.includes(" from ") || targetExports.has(line)).join(`
4554
+ `);
4555
+ }
4556
+ function removeProjectModuleRows(content, targetContent) {
4557
+ const neutralNote = extractProjectModuleNote(targetContent);
4558
+ const withoutProjectRows = content.split(`
4559
+ `).filter((line) => !/^\|.*\|\s*产品模块\s*\|.*\|$/.test(line)).join(`
4560
+ `);
4561
+ if (!neutralNote || withoutProjectRows.includes(neutralNote.trim())) {
4562
+ return withoutProjectRows;
4563
+ }
4564
+ return replaceKnown(withoutProjectRows, `
4565
+ 精确生成来源和选择记录在`, `
4566
+ ${neutralNote}
4567
+ 精确生成来源和选择记录在`);
4568
+ }
4569
+ function extractProjectModuleNote(content) {
4570
+ const start = content.indexOf("项目自有业务模块以");
4571
+ const end = content.indexOf(`
4572
+ 精确生成来源和选择记录在`, start);
4573
+ return start < 0 || end < 0 ? undefined : content.slice(start, end);
4574
+ }
4497
4575
  function repairKnown026Downstream(input) {
4498
4576
  if (input.sourceVersion !== "0.2.26")
4499
4577
  return;
@@ -5458,9 +5536,301 @@ function compareVersions2(left, right) {
5458
5536
  return 0;
5459
5537
  }
5460
5538
 
5539
+ // src/module-schema/cli.ts
5540
+ import { resolve as resolve7 } from "node:path";
5541
+
5542
+ // src/module-schema/handoff.ts
5543
+ import {
5544
+ access as access4,
5545
+ cp,
5546
+ mkdir as mkdir8,
5547
+ mkdtemp as mkdtemp3,
5548
+ readFile as readFile18,
5549
+ readdir as readdir7,
5550
+ rename,
5551
+ rm as rm9,
5552
+ writeFile as writeFile13
5553
+ } from "node:fs/promises";
5554
+ import { dirname as dirname5, join as join18, relative as relative4 } from "node:path";
5555
+
5556
+ // src/module-schema/config.ts
5557
+ import { readFile as readFile17 } from "node:fs/promises";
5558
+ import { isAbsolute, relative as relative3, resolve as resolve6 } from "node:path";
5559
+ async function readModuleSchemaLineageConfig(projectDirectory, configPath) {
5560
+ const absolutePath = resolveProjectPath(projectDirectory, configPath);
5561
+ const parsed = JSON.parse(await readFile17(absolutePath, "utf8"));
5562
+ if (!isConfig(parsed)) {
5563
+ throw new Error("Module schema lineage 配置无效");
5564
+ }
5565
+ validatePaths(projectDirectory, parsed);
5566
+ validateUniqueness(parsed);
5567
+ if (parsed.modules.length === 0) {
5568
+ throw new Error("Module schema lineage 配置没有待交接的业务模块");
5569
+ }
5570
+ return parsed;
5571
+ }
5572
+ function resolveProjectPath(projectDirectory, path2) {
5573
+ if (isAbsolute(path2))
5574
+ throw new Error(`路径必须相对项目根目录:${path2}`);
5575
+ const resolved = resolve6(projectDirectory, path2);
5576
+ const projectRelative = relative3(projectDirectory, resolved);
5577
+ if (projectRelative.startsWith("..") || isAbsolute(projectRelative)) {
5578
+ throw new Error(`路径越出项目根目录:${path2}`);
5579
+ }
5580
+ return resolved;
5581
+ }
5582
+ function isConfig(value) {
5583
+ if (!value || typeof value !== "object")
5584
+ return false;
5585
+ const config = value;
5586
+ return config.schemaVersion === 1 && isPlatform(config.platform) && Array.isArray(config.modules) && config.modules.every(isModule);
5587
+ }
5588
+ function isPlatform(value) {
5589
+ if (!value || typeof value !== "object")
5590
+ return false;
5591
+ const platform = value;
5592
+ return isStringList(platform.schema) && typeof platform.out === "string";
5593
+ }
5594
+ function isModule(value) {
5595
+ if (!value || typeof value !== "object")
5596
+ return false;
5597
+ const module3 = value;
5598
+ return typeof module3.key === "string" && /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/.test(module3.key) && isStringList(module3.schema) && typeof module3.out === "string" && isStringList(module3.legacyPlatformTables) && module3.legacyPlatformTables.every(isTableName);
5599
+ }
5600
+ function isStringList(value) {
5601
+ return Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.length > 0);
5602
+ }
5603
+ function isTableName(value) {
5604
+ return /^[a-z_][a-z0-9_]*(?:\.[a-z_][a-z0-9_]*)?$/.test(value);
5605
+ }
5606
+ function validatePaths(projectDirectory, config) {
5607
+ for (const path2 of [
5608
+ ...config.platform.schema,
5609
+ config.platform.out,
5610
+ ...config.modules.flatMap(({ schema, out }) => [...schema, out])
5611
+ ]) {
5612
+ resolveProjectPath(projectDirectory, path2);
5613
+ }
5614
+ const platformOut = resolveProjectPath(projectDirectory, config.platform.out);
5615
+ for (const module3 of config.modules) {
5616
+ const moduleOut = resolveProjectPath(projectDirectory, module3.out);
5617
+ if (moduleOut === platformOut || moduleOut.startsWith(`${platformOut}/`) || platformOut.startsWith(`${moduleOut}/`)) {
5618
+ throw new Error(`${module3.key} 的 lineage 输出不能与平台目录重叠`);
5619
+ }
5620
+ }
5621
+ }
5622
+ function validateUniqueness(config) {
5623
+ const keys = new Set;
5624
+ const outputs = new Set;
5625
+ const tables = new Set;
5626
+ for (const module3 of config.modules) {
5627
+ if (keys.has(module3.key))
5628
+ throw new Error(`Module key 重复:${module3.key}`);
5629
+ if (outputs.has(module3.out))
5630
+ throw new Error(`Module lineage 输出重复:${module3.out}`);
5631
+ keys.add(module3.key);
5632
+ outputs.add(module3.out);
5633
+ for (const table of module3.legacyPlatformTables) {
5634
+ if (tables.has(table))
5635
+ throw new Error(`旧平台表重复声明:${table}`);
5636
+ tables.add(table);
5637
+ }
5638
+ }
5639
+ }
5640
+
5641
+ // src/module-schema/sql.ts
5642
+ function validatePlatformHandoffSql(sql, allowedTables) {
5643
+ const allowed = new Set(allowedTables);
5644
+ const dropped = new Set;
5645
+ const statements = sql.replaceAll("--> statement-breakpoint", "").split(";").map((statement) => statement.trim()).filter(Boolean);
5646
+ for (const statement of statements) {
5647
+ const match = statement.match(/^DROP TABLE(?: IF EXISTS)? (?:(?:"([^"]+)")\.)?"([^"]+)"(?: CASCADE)?$/i);
5648
+ if (!match) {
5649
+ throw new Error(`交接差异包含非 DROP TABLE DDL:${statement}`);
5650
+ }
5651
+ const table = normalizeTableName(match[1], match[2]);
5652
+ if (!allowed.has(table)) {
5653
+ throw new Error(`交接差异试图删除未声明的表:${table}`);
5654
+ }
5655
+ dropped.add(table);
5656
+ }
5657
+ const missing = [...allowed].filter((table) => !dropped.has(table));
5658
+ if (missing.length > 0) {
5659
+ throw new Error(`平台 snapshot 未包含声明的旧表:${missing.join("、")}`);
5660
+ }
5661
+ }
5662
+ function metadataOnlySql(kind) {
5663
+ const owner = kind === "platform" ? "platform lineage release" : "module lineage baseline";
5664
+ return `-- Windy ${owner}.
5665
+ -- Metadata-only migration: intentionally no executable DDL.
5666
+ `;
5667
+ }
5668
+ function normalizeTableName(schema, table) {
5669
+ return schema && schema !== "public" ? `${schema}.${table}` : table;
5670
+ }
5671
+
5672
+ // src/module-schema/handoff.ts
5673
+ async function handoffModuleSchemaLineages(options) {
5674
+ const project = options.projectDirectory;
5675
+ await assertCleanGit(project);
5676
+ const config = await readModuleSchemaLineageConfig(project, options.configPath || "module-schema.lineages.json");
5677
+ await validateSchemaSources(project, config);
5678
+ const temporaryRoot = await createTemporaryRoot(project);
5679
+ const execute = options.execute || executeDrizzleGenerate;
5680
+ try {
5681
+ const stagedModules = await stageModuleLineages(project, temporaryRoot, config, execute);
5682
+ const stagedPlatform = await stagePlatformLineage(project, temporaryRoot, config, execute);
5683
+ await installStagedLineages(project, temporaryRoot, config, stagedModules, stagedPlatform);
5684
+ } finally {
5685
+ await rm9(temporaryRoot, { recursive: true, force: true });
5686
+ }
5687
+ return {
5688
+ moduleCount: config.modules.length,
5689
+ tableCount: config.modules.reduce((total, module3) => total + module3.legacyPlatformTables.length, 0)
5690
+ };
5691
+ }
5692
+ async function stageModuleLineages(project, temporaryRoot, config, execute) {
5693
+ const outputs = [];
5694
+ for (const [index, module3] of config.modules.entries()) {
5695
+ const output = join18(temporaryRoot, `module-${index}`);
5696
+ await mkdir8(output, { recursive: true });
5697
+ const generatedConfig = join18(temporaryRoot, `module-${index}.config.ts`);
5698
+ await writeDrizzleConfig(generatedConfig, project, module3.schema, relative4(temporaryRoot, output));
5699
+ await execute(generatedConfig, temporaryRoot);
5700
+ const sqlFiles = await listSqlFiles(output);
5701
+ if (sqlFiles.length !== 1) {
5702
+ throw new Error(`${module3.key} baseline 必须恰好生成一个 migration`);
5703
+ }
5704
+ await writeFile13(join18(output, sqlFiles[0]), metadataOnlySql("module"));
5705
+ await writeFile13(join18(output, "lineage.json"), `${JSON.stringify({
5706
+ schemaVersion: 1,
5707
+ moduleKey: module3.key,
5708
+ schema: module3.schema,
5709
+ legacyPlatformTables: module3.legacyPlatformTables,
5710
+ productionExecutor: "module-migration-bundle"
5711
+ }, null, 2)}
5712
+ `);
5713
+ outputs.push(output);
5714
+ }
5715
+ return outputs;
5716
+ }
5717
+ async function stagePlatformLineage(project, temporaryRoot, config, execute) {
5718
+ const source = resolveProjectPath(project, config.platform.out);
5719
+ const output = join18(temporaryRoot, "platform");
5720
+ await cp(source, output, { recursive: true });
5721
+ const before = new Set(await listSqlFiles(output));
5722
+ const generatedConfig = join18(temporaryRoot, "platform.config.ts");
5723
+ await writeDrizzleConfig(generatedConfig, project, config.platform.schema, relative4(temporaryRoot, output));
5724
+ await execute(generatedConfig, temporaryRoot);
5725
+ const created = (await listSqlFiles(output)).filter((path2) => !before.has(path2));
5726
+ if (created.length !== 1) {
5727
+ throw new Error("平台 lineage handoff 必须恰好生成一个 migration");
5728
+ }
5729
+ const sqlPath = join18(output, created[0]);
5730
+ const allowed = config.modules.flatMap(({ legacyPlatformTables }) => legacyPlatformTables);
5731
+ validatePlatformHandoffSql(await readFile18(sqlPath, "utf8"), allowed);
5732
+ await writeFile13(sqlPath, metadataOnlySql("platform"));
5733
+ return output;
5734
+ }
5735
+ async function installStagedLineages(project, temporaryRoot, config, stagedModules, stagedPlatform) {
5736
+ const platform = resolveProjectPath(project, config.platform.out);
5737
+ const backup = join18(temporaryRoot, "platform-backup");
5738
+ const installed = [];
5739
+ await rename(platform, backup);
5740
+ try {
5741
+ await rename(stagedPlatform, platform);
5742
+ for (const [index, module3] of config.modules.entries()) {
5743
+ const output = resolveProjectPath(project, module3.out);
5744
+ if (await exists2(output)) {
5745
+ throw new Error(`${module3.key} lineage 输出已存在,禁止覆盖`);
5746
+ }
5747
+ await mkdir8(dirname5(output), { recursive: true });
5748
+ await rename(stagedModules[index], output);
5749
+ installed.push(output);
5750
+ }
5751
+ } catch (error) {
5752
+ await rm9(platform, { recursive: true, force: true });
5753
+ await rename(backup, platform);
5754
+ for (const output of installed)
5755
+ await rm9(output, { recursive: true, force: true });
5756
+ throw error;
5757
+ }
5758
+ }
5759
+ async function writeDrizzleConfig(path2, project, schemas, out) {
5760
+ const absoluteSchemas = schemas.map((schema) => resolveProjectPath(project, schema));
5761
+ await writeFile13(path2, `import { defineConfig } from "drizzle-kit";
5762
+ export default defineConfig(${JSON.stringify({
5763
+ dialect: "postgresql",
5764
+ schema: absoluteSchemas,
5765
+ out,
5766
+ casing: "snake_case"
5767
+ })});
5768
+ `);
5769
+ }
5770
+ async function validateSchemaSources(project, config) {
5771
+ for (const schema of [
5772
+ ...config.platform.schema,
5773
+ ...config.modules.flatMap(({ schema: schema2 }) => schema2)
5774
+ ]) {
5775
+ await access4(resolveProjectPath(project, schema));
5776
+ }
5777
+ for (const module3 of config.modules) {
5778
+ if (await exists2(resolveProjectPath(project, module3.out))) {
5779
+ throw new Error(`${module3.key} lineage 输出已存在,禁止覆盖`);
5780
+ }
5781
+ }
5782
+ }
5783
+ async function createTemporaryRoot(project) {
5784
+ const windy = join18(project, ".windy");
5785
+ await mkdir8(windy, { recursive: true });
5786
+ return await mkdtemp3(join18(windy, "module-schema-handoff-"));
5787
+ }
5788
+ async function listSqlFiles(root) {
5789
+ return (await readdir7(root)).filter((path2) => path2.endsWith(".sql")).sort();
5790
+ }
5791
+ async function executeDrizzleGenerate(configPath, cwd) {
5792
+ await assertProcess("bunx", ["--bun", "drizzle-kit", "generate", "--config", configPath], cwd, true);
5793
+ }
5794
+ async function assertCleanGit(project) {
5795
+ const result2 = await captureProcess("git", ["status", "--porcelain"], project);
5796
+ if (result2.code !== 0 || result2.stdout.trim()) {
5797
+ throw new Error("schema lineage handoff 前必须保持 Git 工作区干净");
5798
+ }
5799
+ }
5800
+ async function exists2(path2) {
5801
+ try {
5802
+ await access4(path2);
5803
+ return true;
5804
+ } catch {
5805
+ return false;
5806
+ }
5807
+ }
5808
+
5809
+ // src/module-schema/cli.ts
5810
+ async function runModuleSchemaCommand(args) {
5811
+ if (args[0] !== "schema")
5812
+ return false;
5813
+ if (args[1] !== "handoff") {
5814
+ throw new Error("当前 schema 命令只支持 handoff");
5815
+ }
5816
+ const projectDirectory = resolve7(readValue3(args, "--cwd") || process.cwd());
5817
+ const result2 = await handoffModuleSchemaLineages({
5818
+ projectDirectory,
5819
+ configPath: readValue3(args, "--config")
5820
+ });
5821
+ console.log(`Module schema lineage 交接完成:${result2.moduleCount} 个模块、${result2.tableCount} 张旧平台 snapshot 表`);
5822
+ return true;
5823
+ }
5824
+ function readValue3(args, flag) {
5825
+ const index = args.indexOf(flag);
5826
+ return index >= 0 ? args[index + 1] : undefined;
5827
+ }
5828
+
5461
5829
  // src/cli.ts
5462
5830
  async function main() {
5463
5831
  const args = process.argv.slice(2);
5832
+ if (await runModuleSchemaCommand(args))
5833
+ return;
5464
5834
  if (await runLifecycleCommand(args))
5465
5835
  return;
5466
5836
  const options = parseArguments(args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-windy",
3
- "version": "0.2.29",
3
+ "version": "0.2.30",
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": "fc15ecb10194385be79afcd4738b71c7966a9272",
4
- "generatorVersion": "0.2.29",
3
+ "templateCommit": "c61be0fc258ae679c0c0b43d7a838688985d7f0f",
4
+ "generatorVersion": "0.2.30",
5
5
  "modules": [
6
6
  {
7
7
  "name": "system",
@@ -107,6 +107,9 @@ create-windy 在本机 `.env` 中随机生成的 `WINDY_BOOTSTRAP_ADMIN_PASSWORD
107
107
  | `system.data-governance` | 未包含 | H2 | 敏感字段、明文访问和治理导出。 |
108
108
  | `work-order` | 未包含 | H2 | 可替换的业务模块接入示例。 |
109
109
 
110
+ 项目自有业务模块以 `apps/server/src/installed-business-modules.ts` 装配的 Module Manifest
111
+ 为准;shared-scaffold README 不复制项目业务 Key,避免平台升级固化产品命名。
112
+
110
113
  精确生成来源和选择记录在 `.windy/generation.json`。当前版本已提供可回滚的
111
114
  `windy update`;通用 `module add/remove` 尚未交付,不要把 Feature Flag 关闭当成
112
115
  模块已经卸载。
@@ -1,5 +1,4 @@
1
- import { workOrders } from "@southwind-ai/database";
2
- import type { WorkOrder } from "@southwind-ai/example-work-order";
1
+ import { workOrders, type WorkOrder } from "@southwind-ai/example-work-order";
3
2
  import type { NodePgDatabase } from "drizzle-orm/node-postgres";
4
3
  import {
5
4
  DrizzleScopedEntityRepository,
@@ -5,16 +5,18 @@ import fixtureSource from "./router-test-fixtures.ts?raw";
5
5
  import { collectRouteAccess } from "./router-test-fixtures";
6
6
 
7
7
  describe("Router shared-scaffold 中性契约", () => {
8
- test("平台登录页与测试快照不固化下游产品命名", () => {
8
+ test("平台登录页使用中性共享页面", () => {
9
9
  expect(routerSource).toContain(
10
10
  'import LoginPage from "@/pages/auth/LoginPage.vue";',
11
11
  );
12
- expect(`${routerSource}\n${fixtureSource}`).not.toMatch(
13
- /LijianLoginPage|lijian\./,
14
- );
12
+ expect(fixtureSource).toContain("collectRouteAccess");
15
13
  });
16
14
 
17
- test("测试快照从项目路由递归派生真实访问键", () => {
15
+ test("测试快照只从项目注入路由递归派生访问键", () => {
16
+ expect(collectRouteAccess([])).toEqual({
17
+ permissionKeys: [],
18
+ featureKeys: [],
19
+ });
18
20
  const projectRoutes: RouteRecordRaw[] = [
19
21
  {
20
22
  path: "/records",