@wneng/create-keel 0.2.0 → 0.3.0

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 (29) hide show
  1. package/dist/index.js +497 -38
  2. package/dist/index.js.map +1 -1
  3. package/package.json +3 -1
  4. package/src/feature/templates/arch/adr.md.eta +31 -0
  5. package/src/feature/templates/backend/design.md.eta +35 -0
  6. package/src/feature/templates/frontend/design.md.eta +34 -0
  7. package/src/feature/templates/pm/prd.md.eta +37 -0
  8. package/src/feature/templates/sample/user-signup/arch/adr.md.eta +48 -0
  9. package/src/feature/templates/sample/user-signup/backend/design.md.eta +77 -0
  10. package/src/feature/templates/sample/user-signup/frontend/design.md.eta +54 -0
  11. package/src/feature/templates/sample/user-signup/pm/prd.md.eta +46 -0
  12. package/src/feature/templates/sample/user-signup/test/test-plan.md.eta +40 -0
  13. package/src/feature/templates/test/test-plan.md.eta +23 -0
  14. package/src/standards/templates/coding-style-dart.md.eta +49 -0
  15. package/src/standards/templates/coding-style-go.md.eta +52 -0
  16. package/src/standards/templates/coding-style-java.md.eta +50 -0
  17. package/src/standards/templates/coding-style-python.md.eta +51 -0
  18. package/src/standards/templates/coding-style-rust.md.eta +52 -0
  19. package/src/standards/templates/coding-style-typescript.md.eta +50 -0
  20. package/src/standards/templates/dependency-cruiser.config.cjs.eta +57 -0
  21. package/src/standards/templates/design-tokens.ts.eta +37 -0
  22. package/src/standards/templates/module-boundaries.md.eta +82 -0
  23. package/src/standards/templates/tech-stack-agent.md.eta +35 -0
  24. package/src/standards/templates/tech-stack-miniapp.md.eta +34 -0
  25. package/src/standards/templates/tech-stack-mobile.md.eta +36 -0
  26. package/src/standards/templates/tech-stack-server.md.eta +50 -0
  27. package/src/standards/templates/tech-stack-web.md.eta +36 -0
  28. package/src/standards/templates/ui-design-system.md.eta +70 -0
  29. package/src/templates/docs-skeleton/files/usage-quickstart.md +11 -0
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/version.ts
4
- var SCAFFOLDER_VERSION = "0.2.0";
4
+ var SCAFFOLDER_VERSION = "0.3.0";
5
5
 
6
6
  // src/schema/options.ts
7
7
  import Ajv from "ajv";
@@ -34,7 +34,8 @@ var OPTIONS_FIELD_ORDER = [
34
34
  "gitLfs",
35
35
  "integrations",
36
36
  "ci",
37
- "roles"
37
+ "roles",
38
+ "multiApp"
38
39
  ];
39
40
  var OPTIONS_SCHEMA = {
40
41
  $schema: "http://json-schema.org/draft-07/schema#",
@@ -56,7 +57,8 @@ var OPTIONS_SCHEMA = {
56
57
  "gitLfs",
57
58
  "integrations",
58
59
  "ci",
59
- "roles"
60
+ "roles",
61
+ "multiApp"
60
62
  ],
61
63
  properties: {
62
64
  projectName: {
@@ -79,7 +81,8 @@ var OPTIONS_SCHEMA = {
79
81
  type: "array",
80
82
  items: { type: "string", enum: [...ROLE_KINDS] },
81
83
  uniqueItems: true
82
- }
84
+ },
85
+ multiApp: { type: "boolean" }
83
86
  }
84
87
  };
85
88
  var ajv = new Ajv({ allErrors: true, strict: true });
@@ -334,6 +337,27 @@ function parseLayerArg(raw) {
334
337
  return out;
335
338
  }
336
339
 
340
+ // src/standards/tier.ts
341
+ var ADOPTION_TIERS = ["lite", "standard", "full"];
342
+ function isAdoptionTier(value) {
343
+ return ADOPTION_TIERS.includes(value);
344
+ }
345
+ function parseTier(input) {
346
+ if (typeof input !== "string" || input.length === 0) {
347
+ throw new UserInputError(
348
+ `invalid --engineering-standards value: ${JSON.stringify(input)}`,
349
+ `\u53EF\u9009\u503C\uFF1A${ADOPTION_TIERS.join(" | ")}`
350
+ );
351
+ }
352
+ if (!isAdoptionTier(input)) {
353
+ throw new UserInputError(
354
+ `invalid --engineering-standards value: ${JSON.stringify(input)}`,
355
+ `\u53EF\u9009\u503C\uFF1A${ADOPTION_TIERS.join(" | ")}`
356
+ );
357
+ }
358
+ return input;
359
+ }
360
+
337
361
  // src/cli.ts
338
362
  async function run(io) {
339
363
  const args = io.argv.slice(2);
@@ -387,6 +411,9 @@ function parseCreateArgs(args) {
387
411
  let ci;
388
412
  let roles;
389
413
  let sampleFeature = false;
414
+ let engineeringStandards;
415
+ let engineeringStandardsExplicit = false;
416
+ let multiApp = false;
390
417
  for (let i = 0; i < args.length; i += 1) {
391
418
  const a = args[i];
392
419
  switch (a) {
@@ -408,6 +435,22 @@ function parseCreateArgs(args) {
408
435
  case "--sample-feature":
409
436
  sampleFeature = true;
410
437
  break;
438
+ case "--multi-app":
439
+ multiApp = true;
440
+ break;
441
+ case "--engineering-standards": {
442
+ const next = args[i + 1];
443
+ if (next === void 0 || next.startsWith("--")) {
444
+ throw new UserInputError(
445
+ "--engineering-standards requires a tier argument",
446
+ `\u53EF\u9009\u503C\uFF1A${ADOPTION_TIERS.join(" | ")}`
447
+ );
448
+ }
449
+ engineeringStandards = parseTier(next);
450
+ engineeringStandardsExplicit = true;
451
+ i += 1;
452
+ break;
453
+ }
411
454
  case "--config": {
412
455
  const next = args[i + 1];
413
456
  if (next === void 0 || next.startsWith("--")) {
@@ -500,7 +543,9 @@ function parseCreateArgs(args) {
500
543
  ...config !== void 0 ? { config } : {},
501
544
  ...ci !== void 0 ? { ci } : {},
502
545
  ...roles !== void 0 ? { roles } : {},
503
- ...sampleFeature ? { sampleFeature: true } : {}
546
+ ...sampleFeature ? { sampleFeature: true } : {},
547
+ ...engineeringStandards !== void 0 ? { engineeringStandards } : {},
548
+ ...multiApp ? { multiApp: true } : {}
504
549
  };
505
550
  if (sampleFeature && dryRun) {
506
551
  throw new UserInputError(
@@ -508,6 +553,12 @@ function parseCreateArgs(args) {
508
553
  "\u8BF7\u53BB\u6389\u4E00\u9879\u540E\u91CD\u8BD5"
509
554
  );
510
555
  }
556
+ if (!engineeringStandardsExplicit && yes) {
557
+ return {
558
+ projectName,
559
+ flags: { ...flags, engineeringStandards: "standard" }
560
+ };
561
+ }
511
562
  return { projectName, flags };
512
563
  }
513
564
  async function dispatchFeature(io, args) {
@@ -569,6 +620,10 @@ function helpText() {
569
620
  " --roles <list> Comma-separated role directories to scaffold",
570
621
  " (" + ROLE_KINDS.join("|") + "; default: none)",
571
622
  " --sample-feature After create, scaffold the user-signup sample feature",
623
+ " --engineering-standards <tier>",
624
+ " Adoption tier for engineering standards: " + ADOPTION_TIERS.join("|"),
625
+ " (default in --yes mode: standard; omitted in interactive mode)",
626
+ " --multi-app Initialise the project in multi-app mode (<env>/apps/<name>/)",
572
627
  " -h, --help Show this help",
573
628
  " -v, --version Show the scaffolder version",
574
629
  "",
@@ -603,7 +658,7 @@ function featureHelpText() {
603
658
  }
604
659
 
605
660
  // src/orchestrator.ts
606
- import * as path6 from "node:path";
661
+ import * as path7 from "node:path";
607
662
 
608
663
  // src/input/defaults.ts
609
664
  var OPTION_DEFAULTS = {
@@ -619,7 +674,8 @@ var OPTION_DEFAULTS = {
619
674
  gitLfs: false,
620
675
  integrations: false,
621
676
  ci: "gitee",
622
- roles: []
677
+ roles: [],
678
+ multiApp: false
623
679
  };
624
680
  function buildDefaultOptions(projectName) {
625
681
  return { projectName, ...OPTION_DEFAULTS };
@@ -664,8 +720,8 @@ var validate = ajv2.compile(
664
720
  function formatIssues() {
665
721
  const errs = validate.errors ?? [];
666
722
  return errs.map((e) => {
667
- const path10 = e.instancePath || (e.params && "missingProperty" in e.params ? `/${String(e.params.missingProperty)}` : "<root>");
668
- return `${path10}: ${e.message ?? "validation error"}`;
723
+ const path11 = e.instancePath || (e.params && "missingProperty" in e.params ? `/${String(e.params.missingProperty)}` : "<root>");
724
+ return `${path11}: ${e.message ?? "validation error"}`;
669
725
  }).join("; ");
670
726
  }
671
727
  async function loadConfigFile(filePath) {
@@ -901,7 +957,8 @@ async function promptForOptions(input) {
901
957
  gitLfs,
902
958
  integrations,
903
959
  ci,
904
- roles
960
+ roles,
961
+ multiApp: OPTION_DEFAULTS.multiApp
905
962
  };
906
963
  }
907
964
  async function chooseAxis(prompter, prefilled, question, domain, def, report, axisName) {
@@ -1163,7 +1220,8 @@ function buildRenderContext(input) {
1163
1220
  year: String(now.getUTCFullYear()),
1164
1221
  generatedAt: now.toISOString(),
1165
1222
  scaffolderVersion: input.scaffolderVersion,
1166
- ...input.feature !== void 0 ? { feature: input.feature } : {}
1223
+ ...input.feature !== void 0 ? { feature: input.feature } : {},
1224
+ ...input.standards !== void 0 ? { standards: input.standards } : {}
1167
1225
  };
1168
1226
  }
1169
1227
  function buildFeatureContext(slug, nextAdrNumber) {
@@ -1204,7 +1262,10 @@ function toStrictContext(ctx) {
1204
1262
  // `feature` is intentionally only attached when the orchestrator
1205
1263
  // populated it; non-feature templates that try to read it will hit
1206
1264
  // the proxy's "undefined template variable" branch.
1207
- ...ctx.feature !== void 0 ? { feature: ctx.feature } : {}
1265
+ ...ctx.feature !== void 0 ? { feature: ctx.feature } : {},
1266
+ // `standards` follows the same pattern; only the engineering-standards
1267
+ // plan populates it.
1268
+ ...ctx.standards !== void 0 ? { standards: ctx.standards } : {}
1208
1269
  };
1209
1270
  return new Proxy(base, {
1210
1271
  get(target, prop, receiver) {
@@ -1386,6 +1447,10 @@ var METADATA_SCHEMA = {
1386
1447
  type: "array",
1387
1448
  items: { type: "string", pattern: FEATURE_SLUG_PATTERN2 },
1388
1449
  uniqueItems: true
1450
+ },
1451
+ engineeringStandards: {
1452
+ type: "string",
1453
+ enum: [...ADOPTION_TIERS]
1389
1454
  }
1390
1455
  }
1391
1456
  };
@@ -1422,7 +1487,8 @@ var METADATA_TOP_ORDER = [
1422
1487
  "generatedAt",
1423
1488
  "options",
1424
1489
  "templateFragments",
1425
- "features"
1490
+ "features",
1491
+ "engineeringStandards"
1426
1492
  ];
1427
1493
  function canonicalOptions(options) {
1428
1494
  const out = {};
@@ -1446,6 +1512,11 @@ function canonicalMetadata(metadata) {
1446
1512
  if (features !== void 0 && features.length > 0) {
1447
1513
  out[key] = [...features];
1448
1514
  }
1515
+ } else if (key === "engineeringStandards") {
1516
+ const tier = metadata.engineeringStandards;
1517
+ if (tier !== void 0) {
1518
+ out[key] = tier;
1519
+ }
1449
1520
  } else {
1450
1521
  out[key] = metadata[key];
1451
1522
  }
@@ -1483,7 +1554,8 @@ function buildMetadata(params) {
1483
1554
  generatedAt: now.toISOString(),
1484
1555
  options: params.options,
1485
1556
  templateFragments: [...params.templateFragments],
1486
- ...params.features !== void 0 ? { features: [...params.features] } : {}
1557
+ ...params.features !== void 0 ? { features: [...params.features] } : {},
1558
+ ...params.engineeringStandards !== void 0 ? { engineeringStandards: params.engineeringStandards } : {}
1487
1559
  };
1488
1560
  }
1489
1561
 
@@ -1493,6 +1565,7 @@ function appendScaffolderMetadata(input) {
1493
1565
  scaffolderVersion: input.scaffolderVersion,
1494
1566
  options: input.options,
1495
1567
  templateFragments: input.templateFragments,
1568
+ ...input.engineeringStandards !== void 0 ? { engineeringStandards: input.engineeringStandards } : {},
1496
1569
  ...input.now !== void 0 ? { now: input.now } : {}
1497
1570
  });
1498
1571
  const file = {
@@ -1695,6 +1768,375 @@ function verbosityFromFlags(flags) {
1695
1768
  return "default";
1696
1769
  }
1697
1770
 
1771
+ // src/standards/plan.ts
1772
+ import { accessSync as accessSync2 } from "node:fs";
1773
+ import * as path6 from "node:path";
1774
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
1775
+
1776
+ // src/standards/stack-detector.ts
1777
+ function backendManifest(opt) {
1778
+ switch (opt) {
1779
+ case "node":
1780
+ return { env: "server", language: "typescript", manifestPath: "server/package.json", packageManager: "npm" };
1781
+ case "java":
1782
+ return { env: "server", language: "java", manifestPath: "server/pom.xml", packageManager: "maven" };
1783
+ case "go":
1784
+ return { env: "server", language: "go", manifestPath: "server/go.mod", packageManager: "gomod" };
1785
+ case "python":
1786
+ return { env: "server", language: "python", manifestPath: "server/pyproject.toml", packageManager: "pip" };
1787
+ case "none":
1788
+ return null;
1789
+ }
1790
+ }
1791
+ function frontendManifest(opt) {
1792
+ switch (opt) {
1793
+ case "react":
1794
+ case "vue":
1795
+ return { env: "web", language: "typescript", manifestPath: "web/package.json", packageManager: "npm" };
1796
+ case "none":
1797
+ return null;
1798
+ }
1799
+ }
1800
+ function mobileManifest(opt) {
1801
+ switch (opt) {
1802
+ case "flutter":
1803
+ return { env: "mobile", language: "dart", manifestPath: "mobile/pubspec.yaml", packageManager: "pub" };
1804
+ case "react-native":
1805
+ return { env: "mobile", language: "typescript", manifestPath: "mobile/package.json", packageManager: "npm" };
1806
+ case "none":
1807
+ return null;
1808
+ }
1809
+ }
1810
+ function miniappManifest(opt) {
1811
+ switch (opt) {
1812
+ case "wechat":
1813
+ return { env: "miniapp", language: "typescript", manifestPath: "miniapp/package.json", packageManager: "npm" };
1814
+ case "none":
1815
+ return null;
1816
+ }
1817
+ }
1818
+ function agentManifest(opt) {
1819
+ switch (opt) {
1820
+ case "rust-desktop":
1821
+ return { env: "agent", language: "rust", manifestPath: "agent/Cargo.toml", packageManager: "cargo" };
1822
+ case "none":
1823
+ return null;
1824
+ }
1825
+ }
1826
+ var SEED_DEPS_NODE_SERVER = [
1827
+ { name: "typescript", version: "5.5.4", policy: "minor" },
1828
+ { name: "@types/node", version: "20.14.10", policy: "minor" }
1829
+ ];
1830
+ var SEED_DEPS_JAVA_SERVER = [
1831
+ { name: "spring-boot-starter-web", version: "3.3.2", policy: "minor" },
1832
+ { name: "spring-boot-starter-validation", version: "3.3.2", policy: "minor" }
1833
+ ];
1834
+ var SEED_DEPS_GO_SERVER = [
1835
+ { name: "github.com/gin-gonic/gin", version: "1.10.0", policy: "minor" }
1836
+ ];
1837
+ var SEED_DEPS_PYTHON_SERVER = [
1838
+ { name: "fastapi", version: "0.111.1", policy: "minor" },
1839
+ { name: "pydantic", version: "2.8.2", policy: "minor" }
1840
+ ];
1841
+ var SEED_DEPS_REACT_WEB = [
1842
+ { name: "react", version: "18.3.1", policy: "minor" },
1843
+ { name: "react-dom", version: "18.3.1", policy: "minor" },
1844
+ { name: "typescript", version: "5.5.4", policy: "minor" }
1845
+ ];
1846
+ var SEED_DEPS_VUE_WEB = [
1847
+ { name: "vue", version: "3.4.34", policy: "minor" },
1848
+ { name: "typescript", version: "5.5.4", policy: "minor" }
1849
+ ];
1850
+ var SEED_DEPS_FLUTTER_MOBILE = [
1851
+ { name: "flutter", version: "3.22.0", policy: "minor" }
1852
+ ];
1853
+ var SEED_DEPS_REACT_NATIVE_MOBILE = [
1854
+ { name: "react-native", version: "0.74.3", policy: "minor" },
1855
+ { name: "react", version: "18.3.1", policy: "minor" }
1856
+ ];
1857
+ var SEED_DEPS_WECHAT_MINIAPP = [
1858
+ { name: "typescript", version: "5.5.4", policy: "minor" }
1859
+ ];
1860
+ var SEED_DEPS_RUST_AGENT = [
1861
+ { name: "tokio", version: "1.39.2", policy: "minor" },
1862
+ { name: "serde", version: "1.0.204", policy: "minor" }
1863
+ ];
1864
+ function seedDeps(options, env) {
1865
+ switch (env) {
1866
+ case "server":
1867
+ switch (options.backend) {
1868
+ case "node":
1869
+ return SEED_DEPS_NODE_SERVER;
1870
+ case "java":
1871
+ return SEED_DEPS_JAVA_SERVER;
1872
+ case "go":
1873
+ return SEED_DEPS_GO_SERVER;
1874
+ case "python":
1875
+ return SEED_DEPS_PYTHON_SERVER;
1876
+ case "none":
1877
+ return [];
1878
+ }
1879
+ break;
1880
+ case "web":
1881
+ switch (options.frontend) {
1882
+ case "react":
1883
+ return SEED_DEPS_REACT_WEB;
1884
+ case "vue":
1885
+ return SEED_DEPS_VUE_WEB;
1886
+ case "none":
1887
+ return [];
1888
+ }
1889
+ break;
1890
+ case "mobile":
1891
+ switch (options.mobile) {
1892
+ case "flutter":
1893
+ return SEED_DEPS_FLUTTER_MOBILE;
1894
+ case "react-native":
1895
+ return SEED_DEPS_REACT_NATIVE_MOBILE;
1896
+ case "none":
1897
+ return [];
1898
+ }
1899
+ break;
1900
+ case "miniapp":
1901
+ switch (options.miniapp) {
1902
+ case "wechat":
1903
+ return SEED_DEPS_WECHAT_MINIAPP;
1904
+ case "none":
1905
+ return [];
1906
+ }
1907
+ break;
1908
+ case "agent":
1909
+ switch (options.agent) {
1910
+ case "rust-desktop":
1911
+ return SEED_DEPS_RUST_AGENT;
1912
+ case "none":
1913
+ return [];
1914
+ }
1915
+ break;
1916
+ }
1917
+ return [];
1918
+ }
1919
+ function detectRuntimeManifests(options) {
1920
+ const detectors = [
1921
+ backendManifest(options.backend),
1922
+ frontendManifest(options.frontend),
1923
+ mobileManifest(options.mobile),
1924
+ miniappManifest(options.miniapp),
1925
+ agentManifest(options.agent)
1926
+ ];
1927
+ return detectors.filter((m) => m !== null).map((m) => ({ ...m, dependencies: seedDeps(options, m.env) }));
1928
+ }
1929
+
1930
+ // src/standards/plan.ts
1931
+ async function buildStandardsPlan(input) {
1932
+ const templatesRoot = input.templatesRoot ?? defaultStandardsTemplatesRoot();
1933
+ const now = input.now ?? /* @__PURE__ */ new Date();
1934
+ const lastReviewed = formatDate(now);
1935
+ const manifests = detectRuntimeManifests(input.options);
1936
+ const files = [];
1937
+ const languages = uniqueLanguages(manifests);
1938
+ for (const language of languages) {
1939
+ files.push(
1940
+ await renderStandardsTemplate({
1941
+ templatesRoot,
1942
+ templateRelPath: `coding-style-${language}.md.eta`,
1943
+ targetPath: `docs/03-\u5DE5\u7A0B\u89C4\u8303\u4E0E\u7814\u53D1\u57FA\u7840\u8BBE\u65BD/coding-style-${language}.md`,
1944
+ contributedBy: `standards:coding-style-${language}`,
1945
+ options: input.options,
1946
+ scaffolderVersion: input.scaffolderVersion,
1947
+ now,
1948
+ standards: { tier: input.tier, language, lastReviewed }
1949
+ })
1950
+ );
1951
+ }
1952
+ if (input.tier === "standard" || input.tier === "full") {
1953
+ for (const m of manifests) {
1954
+ const fileBaseName = `tech-stack-${m.env}.md`;
1955
+ files.push(
1956
+ await renderStandardsTemplate({
1957
+ templatesRoot,
1958
+ templateRelPath: `tech-stack-${m.env}.md.eta`,
1959
+ targetPath: `docs/03-\u5DE5\u7A0B\u89C4\u8303\u4E0E\u7814\u53D1\u57FA\u7840\u8BBE\u65BD/${fileBaseName}`,
1960
+ contributedBy: `standards:tech-stack-${m.env}`,
1961
+ options: input.options,
1962
+ scaffolderVersion: input.scaffolderVersion,
1963
+ now,
1964
+ standards: {
1965
+ tier: input.tier,
1966
+ env: m.env,
1967
+ language: m.language,
1968
+ packageManager: m.packageManager,
1969
+ manifestPath: m.manifestPath,
1970
+ lastReviewed,
1971
+ multiApp: input.options.multiApp
1972
+ // Smuggle dependencies through standards context so the
1973
+ // template can iterate. We attach via Object.assign-style
1974
+ // cast because StandardsRenderContext does not declare it
1975
+ // (deps are only read by the tech-stack templates and the
1976
+ // shape varies per env, so keeping it untyped here is
1977
+ // acceptable).
1978
+ },
1979
+ extraStandardsFields: { dependencies: m.dependencies }
1980
+ })
1981
+ );
1982
+ }
1983
+ files.push(
1984
+ await renderStandardsTemplate({
1985
+ templatesRoot,
1986
+ templateRelPath: "module-boundaries.md.eta",
1987
+ targetPath: "docs/03-\u5DE5\u7A0B\u89C4\u8303\u4E0E\u7814\u53D1\u57FA\u7840\u8BBE\u65BD/module-boundaries.md",
1988
+ contributedBy: "standards:module-boundaries",
1989
+ options: input.options,
1990
+ scaffolderVersion: input.scaffolderVersion,
1991
+ now,
1992
+ standards: {
1993
+ tier: input.tier,
1994
+ multiApp: input.options.multiApp,
1995
+ lastReviewed
1996
+ }
1997
+ })
1998
+ );
1999
+ if (input.options.multiApp && hasJsTsEnvironment(manifests)) {
2000
+ files.push(
2001
+ await renderStandardsTemplate({
2002
+ templatesRoot,
2003
+ templateRelPath: "dependency-cruiser.config.cjs.eta",
2004
+ targetPath: "dependency-cruiser.config.cjs",
2005
+ contributedBy: "standards:dep-cruiser",
2006
+ options: input.options,
2007
+ scaffolderVersion: input.scaffolderVersion,
2008
+ now,
2009
+ standards: {
2010
+ tier: input.tier,
2011
+ multiApp: true,
2012
+ lastReviewed
2013
+ }
2014
+ })
2015
+ );
2016
+ }
2017
+ }
2018
+ if (input.tier === "full" && hasFrontendSurface(input.options)) {
2019
+ files.push(
2020
+ await renderStandardsTemplate({
2021
+ templatesRoot,
2022
+ templateRelPath: "ui-design-system.md.eta",
2023
+ targetPath: "docs/05-\u524D\u7AEF\u5BA2\u6237\u7AEF\u8BE6\u7EC6\u8BBE\u8BA1/ui-design-system.md",
2024
+ contributedBy: "standards:ui-design-system",
2025
+ options: input.options,
2026
+ scaffolderVersion: input.scaffolderVersion,
2027
+ now,
2028
+ standards: { tier: "full", lastReviewed }
2029
+ })
2030
+ );
2031
+ if (input.options.frontend !== "none") {
2032
+ files.push(
2033
+ await renderStandardsTemplate({
2034
+ templatesRoot,
2035
+ templateRelPath: "design-tokens.ts.eta",
2036
+ targetPath: "web/src/design-tokens.ts",
2037
+ contributedBy: "standards:design-tokens",
2038
+ options: input.options,
2039
+ scaffolderVersion: input.scaffolderVersion,
2040
+ now,
2041
+ standards: { tier: "full", lastReviewed }
2042
+ })
2043
+ );
2044
+ }
2045
+ }
2046
+ const sortedFiles = [...files].sort(
2047
+ (a, b) => a.targetPath < b.targetPath ? -1 : a.targetPath > b.targetPath ? 1 : 0
2048
+ );
2049
+ const directories = collectDirectories(sortedFiles);
2050
+ return { files: sortedFiles, directories };
2051
+ }
2052
+ async function renderStandardsTemplate(input) {
2053
+ const templatePath = path6.join(input.templatesRoot, input.templateRelPath);
2054
+ const standards = {
2055
+ ...input.standards,
2056
+ ...input.extraStandardsFields ?? {}
2057
+ };
2058
+ const ctx = buildRenderContext({
2059
+ options: input.options,
2060
+ scaffolderVersion: input.scaffolderVersion,
2061
+ now: input.now,
2062
+ standards
2063
+ });
2064
+ let content;
2065
+ try {
2066
+ content = await renderFile(templatePath, ctx);
2067
+ } catch (e) {
2068
+ throw new TemplateError(
2069
+ `engineering-standards template render failed: ${input.templateRelPath}: ${e.message}`,
2070
+ "\u8BF7\u786E\u8BA4 src/standards/templates/ \u4E0B\u5B58\u5728 " + input.templateRelPath
2071
+ );
2072
+ }
2073
+ return {
2074
+ targetPath: input.targetPath,
2075
+ content,
2076
+ contributedBy: input.contributedBy
2077
+ };
2078
+ }
2079
+ function uniqueLanguages(manifests) {
2080
+ const seen = /* @__PURE__ */ new Set();
2081
+ const out = [];
2082
+ for (const m of manifests) {
2083
+ if (!seen.has(m.language)) {
2084
+ seen.add(m.language);
2085
+ out.push(m.language);
2086
+ }
2087
+ }
2088
+ return out;
2089
+ }
2090
+ function hasJsTsEnvironment(manifests) {
2091
+ return manifests.some((m) => m.language === "typescript");
2092
+ }
2093
+ function hasFrontendSurface(options) {
2094
+ if (options.frontend !== "none") return true;
2095
+ if (options.mobile === "react-native" || options.mobile === "flutter") return true;
2096
+ if (options.miniapp !== "none") return true;
2097
+ return false;
2098
+ }
2099
+ function formatDate(d) {
2100
+ const y = d.getUTCFullYear();
2101
+ const m = String(d.getUTCMonth() + 1).padStart(2, "0");
2102
+ const day = String(d.getUTCDate()).padStart(2, "0");
2103
+ return `${y}-${m}-${day}`;
2104
+ }
2105
+ function collectDirectories(files) {
2106
+ const set = /* @__PURE__ */ new Set();
2107
+ for (const f of files) {
2108
+ let cur = path6.posix.dirname(f.targetPath);
2109
+ while (cur && cur !== "." && cur !== "/") {
2110
+ set.add(cur);
2111
+ cur = path6.posix.dirname(cur);
2112
+ }
2113
+ }
2114
+ return [...set].sort();
2115
+ }
2116
+ function defaultStandardsTemplatesRoot() {
2117
+ const here = path6.dirname(fileURLToPath2(import.meta.url));
2118
+ const pkgRoot = findPackageRoot2(here);
2119
+ return path6.join(pkgRoot, "src", "standards", "templates");
2120
+ }
2121
+ function findPackageRoot2(start) {
2122
+ let dir = start;
2123
+ for (let i = 0; i < 10; i += 1) {
2124
+ const candidate = path6.join(dir, "package.json");
2125
+ try {
2126
+ accessSync2(candidate);
2127
+ return dir;
2128
+ } catch {
2129
+ }
2130
+ const parent = path6.dirname(dir);
2131
+ if (parent === dir) break;
2132
+ dir = parent;
2133
+ }
2134
+ throw new TemplateError(
2135
+ `unable to locate package.json above ${start}`,
2136
+ "\u8BF7\u786E\u8BA4 scaffolder \u5B89\u88C5\u5B8C\u6574\uFF0Csrc/standards/templates/ \u4E0E package.json \u4F4D\u4E8E\u540C\u4E00\u5C42\u7EA7"
2137
+ );
2138
+ }
2139
+
1698
2140
  // src/orchestrator.ts
1699
2141
  async function runCreate(input) {
1700
2142
  const reporter = new Reporter(
@@ -1722,17 +2164,34 @@ async function runCreate(input) {
1722
2164
  name: f.manifest.name,
1723
2165
  version: f.manifest.version
1724
2166
  }));
2167
+ if (input.flags.engineeringStandards !== void 0) {
2168
+ const standardsPlan = await buildStandardsPlan({
2169
+ options,
2170
+ tier: input.flags.engineeringStandards,
2171
+ scaffolderVersion: SCAFFOLDER_VERSION,
2172
+ ...input.io.now !== void 0 ? { now: input.io.now } : {}
2173
+ });
2174
+ plan = {
2175
+ files: [...plan.files, ...standardsPlan.files].sort(
2176
+ (a, b) => a.targetPath < b.targetPath ? -1 : a.targetPath > b.targetPath ? 1 : 0
2177
+ ),
2178
+ directories: [
2179
+ .../* @__PURE__ */ new Set([...plan.directories, ...standardsPlan.directories])
2180
+ ].sort()
2181
+ };
2182
+ }
1725
2183
  plan = appendScaffolderMetadata({
1726
2184
  plan,
1727
2185
  scaffolderVersion: SCAFFOLDER_VERSION,
1728
2186
  options,
1729
2187
  templateFragments: fragmentRefs,
2188
+ ...input.flags.engineeringStandards !== void 0 ? { engineeringStandards: input.flags.engineeringStandards } : {},
1730
2189
  ...input.io.now !== void 0 ? { now: input.io.now } : {}
1731
2190
  });
1732
2191
  validatePlanSyntax(plan);
1733
2192
  reporter.info(`planned ${plan.files.length} file(s) across ${plan.directories.length} directory`);
1734
2193
  const cwd = input.io.cwd ?? process.cwd();
1735
- const targetDirectory = path6.resolve(cwd, options.projectName);
2194
+ const targetDirectory = path7.resolve(cwd, options.projectName);
1736
2195
  if (input.flags.dryRun) {
1737
2196
  reporter.stage("dry-run");
1738
2197
  reporter.dryRunReport(plan);
@@ -1793,12 +2252,12 @@ async function resolveOptions(input) {
1793
2252
 
1794
2253
  // src/feature/orchestrator.ts
1795
2254
  import { promises as fs8 } from "node:fs";
1796
- import * as path9 from "node:path";
2255
+ import * as path10 from "node:path";
1797
2256
 
1798
2257
  // src/feature/plan.ts
1799
- import { promises as fs7, accessSync as accessSync2 } from "node:fs";
1800
- import * as path7 from "node:path";
1801
- import { fileURLToPath as fileURLToPath2 } from "node:url";
2258
+ import { promises as fs7, accessSync as accessSync3 } from "node:fs";
2259
+ import * as path8 from "node:path";
2260
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
1802
2261
 
1803
2262
  // src/feature/openapi-edit.ts
1804
2263
  import { isMap, isScalar, parseDocument, YAMLMap } from "yaml";
@@ -1892,7 +2351,7 @@ function slugToOperationId(slug) {
1892
2351
  // src/feature/plan.ts
1893
2352
  async function buildFeaturePlan(input) {
1894
2353
  const templatesRoot = input.templatesRoot ?? defaultFeatureTemplatesRoot();
1895
- const sourceRoot = input.source === "sample" ? path7.join(templatesRoot, "sample", input.slug) : templatesRoot;
2354
+ const sourceRoot = input.source === "sample" ? path8.join(templatesRoot, "sample", input.slug) : templatesRoot;
1896
2355
  const nextAdrNumber = await computeAdrNumberForSlug(input.cwd, input.slug);
1897
2356
  const renderContext = buildRenderContext({
1898
2357
  options: input.options,
@@ -1906,7 +2365,7 @@ async function buildFeaturePlan(input) {
1906
2365
  const artifact = LAYER_ARTIFACTS[layer];
1907
2366
  const targetPath = computeTargetPath(layer, input.slug, nextAdrNumber);
1908
2367
  outputs.set(layer, targetPath);
1909
- const templatePath = path7.join(sourceRoot, artifact.placeholderTemplate);
2368
+ const templatePath = path8.join(sourceRoot, artifact.placeholderTemplate);
1910
2369
  const content = await renderFeatureTemplate(templatePath, renderContext, layer, input.source);
1911
2370
  files.push({
1912
2371
  targetPath,
@@ -1918,7 +2377,7 @@ async function buildFeaturePlan(input) {
1918
2377
  let openapiAlreadyExisted;
1919
2378
  if (input.layers.includes("backend") && input.options.contract !== "none") {
1920
2379
  const apiPath = "contracts/openapi/api.yaml";
1921
- const apiAbs = path7.join(input.cwd, apiPath);
2380
+ const apiAbs = path8.join(input.cwd, apiPath);
1922
2381
  let currentText;
1923
2382
  try {
1924
2383
  currentText = await fs7.readFile(apiAbs, "utf8");
@@ -1990,7 +2449,7 @@ async function buildFeaturePlan(input) {
1990
2449
  const sortedFiles = [...files].sort(
1991
2450
  (a, b) => a.targetPath < b.targetPath ? -1 : a.targetPath > b.targetPath ? 1 : 0
1992
2451
  );
1993
- const directories = collectDirectories(sortedFiles);
2452
+ const directories = collectDirectories2(sortedFiles);
1994
2453
  return {
1995
2454
  files: sortedFiles,
1996
2455
  directories,
@@ -2017,7 +2476,7 @@ async function renderFeatureTemplate(templatePath, context, layer, source) {
2017
2476
  }
2018
2477
  var ADR_FILE_RE = /^adr-(\d{4,})-/;
2019
2478
  async function computeAdrNumberForSlug(cwd, slug) {
2020
- const dir = path7.join(cwd, "docs", "02-\u7CFB\u7EDF\u65B9\u6848\u4E0E\u67B6\u6784");
2479
+ const dir = path8.join(cwd, "docs", "02-\u7CFB\u7EDF\u65B9\u6848\u4E0E\u67B6\u6784");
2021
2480
  let entries;
2022
2481
  try {
2023
2482
  entries = await fs7.readdir(dir);
@@ -2042,32 +2501,32 @@ async function computeAdrNumberForSlug(cwd, slug) {
2042
2501
  function escapeRegex(s) {
2043
2502
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2044
2503
  }
2045
- function collectDirectories(files) {
2504
+ function collectDirectories2(files) {
2046
2505
  const set = /* @__PURE__ */ new Set();
2047
2506
  for (const f of files) {
2048
- let cur = path7.posix.dirname(f.targetPath);
2507
+ let cur = path8.posix.dirname(f.targetPath);
2049
2508
  while (cur && cur !== "." && cur !== "/") {
2050
2509
  set.add(cur);
2051
- cur = path7.posix.dirname(cur);
2510
+ cur = path8.posix.dirname(cur);
2052
2511
  }
2053
2512
  }
2054
2513
  return [...set].sort();
2055
2514
  }
2056
2515
  function defaultFeatureTemplatesRoot() {
2057
- const here = path7.dirname(fileURLToPath2(import.meta.url));
2058
- const pkgRoot = findPackageRoot2(here);
2059
- return path7.join(pkgRoot, "src", "feature", "templates");
2516
+ const here = path8.dirname(fileURLToPath3(import.meta.url));
2517
+ const pkgRoot = findPackageRoot3(here);
2518
+ return path8.join(pkgRoot, "src", "feature", "templates");
2060
2519
  }
2061
- function findPackageRoot2(start) {
2520
+ function findPackageRoot3(start) {
2062
2521
  let dir = start;
2063
2522
  for (let i = 0; i < 10; i += 1) {
2064
- const candidate = path7.join(dir, "package.json");
2523
+ const candidate = path8.join(dir, "package.json");
2065
2524
  try {
2066
- accessSync2(candidate);
2525
+ accessSync3(candidate);
2067
2526
  return dir;
2068
2527
  } catch {
2069
2528
  }
2070
- const parent = path7.dirname(dir);
2529
+ const parent = path8.dirname(dir);
2071
2530
  if (parent === dir) break;
2072
2531
  dir = parent;
2073
2532
  }
@@ -2078,11 +2537,11 @@ function findPackageRoot2(start) {
2078
2537
  }
2079
2538
 
2080
2539
  // src/feature/manifest-update.ts
2081
- import * as path8 from "node:path";
2540
+ import * as path9 from "node:path";
2082
2541
  async function appendFeatureSlug(projectDirectory, slug) {
2083
2542
  const manifest = await loadMetadataFile(projectDirectory);
2084
2543
  const next = withFeatureSlug(manifest, slug);
2085
- const metadataPath = path8.join(projectDirectory, METADATA_FILENAME);
2544
+ const metadataPath = path9.join(projectDirectory, METADATA_FILENAME);
2086
2545
  if (next === manifest) {
2087
2546
  return { updated: false, metadataPath };
2088
2547
  }
@@ -2115,7 +2574,7 @@ async function runFeatureAdd(input) {
2115
2574
  const code = e.code;
2116
2575
  if (code === "ENOENT") {
2117
2576
  throw new UserInputError(
2118
- `no .scaffolder.json found at ${path9.join(input.cwd, METADATA_FILENAME)}`,
2577
+ `no .scaffolder.json found at ${path10.join(input.cwd, METADATA_FILENAME)}`,
2119
2578
  "\u8BF7\u5728 keel \u9879\u76EE\u6839\u76EE\u5F55\u8FD0\u884C\uFF1B\u6216\u5148\u7528 create-keel create \u521B\u5EFA\u9879\u76EE"
2120
2579
  );
2121
2580
  }
@@ -2170,7 +2629,7 @@ async function splitPlanByExistence(plan, cwd, force) {
2170
2629
  const toWrite = [];
2171
2630
  const skipped = [];
2172
2631
  for (const file of plan.files) {
2173
- const exists = await fileExists(path9.join(cwd, file.targetPath));
2632
+ const exists = await fileExists(path10.join(cwd, file.targetPath));
2174
2633
  if (exists) {
2175
2634
  skipped.push(file.targetPath);
2176
2635
  } else {