@wneng/create-keel 0.2.1 → 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.
- package/dist/index.js +497 -38
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/standards/templates/coding-style-dart.md.eta +49 -0
- package/src/standards/templates/coding-style-go.md.eta +52 -0
- package/src/standards/templates/coding-style-java.md.eta +50 -0
- package/src/standards/templates/coding-style-python.md.eta +51 -0
- package/src/standards/templates/coding-style-rust.md.eta +52 -0
- package/src/standards/templates/coding-style-typescript.md.eta +50 -0
- package/src/standards/templates/dependency-cruiser.config.cjs.eta +57 -0
- package/src/standards/templates/design-tokens.ts.eta +37 -0
- package/src/standards/templates/module-boundaries.md.eta +82 -0
- package/src/standards/templates/tech-stack-agent.md.eta +35 -0
- package/src/standards/templates/tech-stack-miniapp.md.eta +34 -0
- package/src/standards/templates/tech-stack-mobile.md.eta +36 -0
- package/src/standards/templates/tech-stack-server.md.eta +50 -0
- package/src/standards/templates/tech-stack-web.md.eta +36 -0
- package/src/standards/templates/ui-design-system.md.eta +70 -0
- 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.
|
|
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
|
|
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
|
|
668
|
-
return `${
|
|
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 =
|
|
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
|
|
2255
|
+
import * as path10 from "node:path";
|
|
1797
2256
|
|
|
1798
2257
|
// src/feature/plan.ts
|
|
1799
|
-
import { promises as fs7, accessSync as
|
|
1800
|
-
import * as
|
|
1801
|
-
import { fileURLToPath as
|
|
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" ?
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
2504
|
+
function collectDirectories2(files) {
|
|
2046
2505
|
const set = /* @__PURE__ */ new Set();
|
|
2047
2506
|
for (const f of files) {
|
|
2048
|
-
let cur =
|
|
2507
|
+
let cur = path8.posix.dirname(f.targetPath);
|
|
2049
2508
|
while (cur && cur !== "." && cur !== "/") {
|
|
2050
2509
|
set.add(cur);
|
|
2051
|
-
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 =
|
|
2058
|
-
const pkgRoot =
|
|
2059
|
-
return
|
|
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
|
|
2520
|
+
function findPackageRoot3(start) {
|
|
2062
2521
|
let dir = start;
|
|
2063
2522
|
for (let i = 0; i < 10; i += 1) {
|
|
2064
|
-
const candidate =
|
|
2523
|
+
const candidate = path8.join(dir, "package.json");
|
|
2065
2524
|
try {
|
|
2066
|
-
|
|
2525
|
+
accessSync3(candidate);
|
|
2067
2526
|
return dir;
|
|
2068
2527
|
} catch {
|
|
2069
2528
|
}
|
|
2070
|
-
const parent =
|
|
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
|
|
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 =
|
|
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 ${
|
|
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(
|
|
2632
|
+
const exists = await fileExists(path10.join(cwd, file.targetPath));
|
|
2174
2633
|
if (exists) {
|
|
2175
2634
|
skipped.push(file.targetPath);
|
|
2176
2635
|
} else {
|