@umec/core 0.1.0-alpha.10 → 0.1.0-alpha.12

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
@@ -14,8 +14,8 @@ import {
14
14
  } from "./chunk-T2PO3YLZ.js";
15
15
 
16
16
  // src/cli/index.ts
17
- import { readFileSync as readFileSync5 } from "fs";
18
- import { join as join5 } from "path";
17
+ import { readFileSync as readFileSync7 } from "fs";
18
+ import { join as join7 } from "path";
19
19
 
20
20
  // src/cli/doctor.ts
21
21
  import { existsSync } from "fs";
@@ -508,17 +508,18 @@ async function runMigrate(options) {
508
508
  }
509
509
  if (options.repair) {
510
510
  for (const upgrade of CORE_UPGRADES) {
511
- const needed = dryRun ? true : await upgrade.canApply(options.executor);
512
- if (!needed) continue;
513
- print(`upgrade ${upgrade.name} (${upgrade.fromVersion} \u2192 ${upgrade.toVersion})`);
514
- if (dryRun) {
511
+ const legacyPresent = dryRun ? true : await upgrade.canApply(options.executor);
512
+ if (legacyPresent) {
513
+ print(`upgrade ${upgrade.name} (${upgrade.fromVersion} \u2192 ${upgrade.toVersion})`);
514
+ if (dryRun) {
515
+ applied.push(upgrade.name);
516
+ continue;
517
+ }
518
+ await upgrade.apply(options.executor);
519
+ await recordMigration(options.executor, upgrade.name, `upgrade:${upgrade.name}`, "upgrade", now);
515
520
  applied.push(upgrade.name);
516
- continue;
521
+ alreadyByName.set(upgrade.name, { name: upgrade.name, checksum: `upgrade:${upgrade.name}`, kind: "upgrade" });
517
522
  }
518
- await upgrade.apply(options.executor);
519
- await recordMigration(options.executor, upgrade.name, `upgrade:${upgrade.name}`, "upgrade", now);
520
- applied.push(upgrade.name);
521
- alreadyByName.set(upgrade.name, { name: upgrade.name, checksum: `upgrade:${upgrade.name}`, kind: "upgrade" });
522
523
  for (const supersededName of ["0002_orders.sql", "0005_orders_tracking.sql"]) {
523
524
  const superseded = coreMigrations.find((migration) => migration.name === supersededName);
524
525
  if (!superseded) {
@@ -581,10 +582,839 @@ function coreVersionFromPackage(root = findPackageRoot()) {
581
582
  return pkg.version;
582
583
  }
583
584
 
585
+ // src/cli/registry.ts
586
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
587
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join5, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
588
+
589
+ // src/cli/template-doctor.ts
590
+ import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync } from "fs";
591
+ import { isAbsolute, join as join4, relative, resolve, sep } from "path";
592
+ var SHA256_HEX = /^[0-9a-f]{64}$/;
593
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
594
+ "node_modules",
595
+ ".git",
596
+ "dist",
597
+ ".astro",
598
+ ".wrangler",
599
+ "coverage",
600
+ ".output",
601
+ ".cache",
602
+ ".pnpm-store"
603
+ ]);
604
+ function isZone(value) {
605
+ return value === "managed" || value === "brand" || value === "generated";
606
+ }
607
+ function isRecord(value) {
608
+ return typeof value === "object" && value !== null && !Array.isArray(value);
609
+ }
610
+ function isHttpsUrl(value) {
611
+ return /^https:\/\//i.test(value);
612
+ }
613
+ function isSafeRelPath(posixPath) {
614
+ if (!posixPath || posixPath.startsWith("/") || posixPath.includes("\\") || posixPath.includes("\0")) {
615
+ return false;
616
+ }
617
+ const parts = posixPath.split("/");
618
+ return parts.every((part) => part !== "" && part !== "." && part !== "..");
619
+ }
620
+ function resolveInstancePath(cwd, posixPath) {
621
+ if (!isSafeRelPath(posixPath)) return null;
622
+ const resolved = resolve(cwd, ...posixPath.split("/"));
623
+ const rel = relative(resolve(cwd), resolved);
624
+ if (!rel || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;
625
+ return resolved;
626
+ }
627
+ function parseTemplateManifest(text) {
628
+ let parsed;
629
+ try {
630
+ parsed = JSON.parse(text);
631
+ } catch {
632
+ throw new Error("manifest \u306E JSON \u304C\u4E0D\u6B63\u3067\u3059\u3002");
633
+ }
634
+ if (!isRecord(parsed)) throw new Error("manifest \u306E JSON \u304C\u4E0D\u6B63\u3067\u3059\u3002");
635
+ if (parsed.schemaVersion !== 1) {
636
+ throw new Error(`manifest schemaVersion ${String(parsed.schemaVersion)} \u306B\u306F\u672A\u5BFE\u5FDC\u3067\u3059\uFF08\u5BFE\u5FDC: 1\uFF09\u3002`);
637
+ }
638
+ if (typeof parsed.templateVersion !== "string" || parsed.templateVersion.length === 0) {
639
+ throw new Error("manifest.templateVersion \u304C\u5FC5\u8981\u3067\u3059\u3002");
640
+ }
641
+ if (typeof parsed.generatedAt !== "string" || parsed.generatedAt.length === 0) {
642
+ throw new Error("manifest.generatedAt \u304C\u5FC5\u8981\u3067\u3059\u3002");
643
+ }
644
+ if (!Array.isArray(parsed.files)) {
645
+ throw new Error("manifest.files \u304C\u5FC5\u8981\u3067\u3059\u3002");
646
+ }
647
+ const files = [];
648
+ const seen = /* @__PURE__ */ new Set();
649
+ for (const entry of parsed.files) {
650
+ if (!isRecord(entry)) throw new Error("manifest.files \u306E\u8981\u7D20\u304C\u4E0D\u6B63\u3067\u3059\u3002");
651
+ if (typeof entry.path !== "string" || !isSafeRelPath(entry.path)) {
652
+ throw new Error(`manifest \u306E path \u304C\u4E0D\u6B63\u3067\u3059: ${String(entry.path)}`);
653
+ }
654
+ if (seen.has(entry.path)) {
655
+ throw new Error(`manifest \u306E path \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${entry.path}`);
656
+ }
657
+ if (typeof entry.sha256 !== "string" || !SHA256_HEX.test(entry.sha256.toLowerCase())) {
658
+ throw new Error(`manifest \u306E sha256 \u304C\u4E0D\u6B63\u3067\u3059: ${entry.path}`);
659
+ }
660
+ if (!isZone(entry.zone)) {
661
+ throw new Error(`manifest \u306E zone \u304C\u4E0D\u6B63\u3067\u3059: ${entry.path} (${String(entry.zone)})`);
662
+ }
663
+ seen.add(entry.path);
664
+ files.push({
665
+ path: entry.path,
666
+ sha256: entry.sha256.toLowerCase(),
667
+ zone: entry.zone
668
+ });
669
+ }
670
+ return {
671
+ schemaVersion: 1,
672
+ templateVersion: parsed.templateVersion,
673
+ generatedAt: parsed.generatedAt,
674
+ files
675
+ };
676
+ }
677
+ async function loadManifestText(source, cwd, fetchImpl = globalThis.fetch) {
678
+ if (isHttpsUrl(source)) {
679
+ const response = await fetchImpl(source, { signal: AbortSignal.timeout(15e3) });
680
+ if (!response.ok) {
681
+ throw new Error(`manifest \u3092\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093 (${response.status}): ${source}`);
682
+ }
683
+ return { text: await response.text(), origin: source };
684
+ }
685
+ if (/^https?:\/\//i.test(source)) {
686
+ throw new Error("manifest URL \u306F https:// \u306E\u307F\u53D7\u3051\u4ED8\u3051\u307E\u3059\u3002");
687
+ }
688
+ const path = isAbsolute(source) ? source : resolve(cwd, source);
689
+ if (!existsSync3(path) || !statSync(path).isFile()) {
690
+ throw new Error(`manifest \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${source}`);
691
+ }
692
+ return { text: readFileSync4(path, "utf8"), origin: path };
693
+ }
694
+ function toPosix(rel) {
695
+ return rel.split(sep).join("/");
696
+ }
697
+ function listInstanceFiles(cwd) {
698
+ const root = resolve(cwd);
699
+ const out = [];
700
+ const walk = (dir) => {
701
+ for (const entry of readdirSync2(dir, { withFileTypes: true })) {
702
+ if (entry.name === ".DS_Store") continue;
703
+ const full = join4(dir, entry.name);
704
+ if (entry.isDirectory()) {
705
+ if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
706
+ walk(full);
707
+ continue;
708
+ }
709
+ if (!entry.isFile()) continue;
710
+ out.push(toPosix(relative(root, full)));
711
+ }
712
+ };
713
+ walk(root);
714
+ out.sort();
715
+ return out;
716
+ }
717
+ function classifyTemplate(input) {
718
+ const files = [];
719
+ const tracked = new Set(input.manifest.files.map((file) => file.path));
720
+ for (const entry of input.manifest.files) {
721
+ if (entry.zone === "generated") continue;
722
+ const resolved = resolveInstancePath(input.cwd, entry.path);
723
+ if (!resolved || !existsSync3(resolved) || !statSync(resolved).isFile()) {
724
+ files.push({ path: entry.path, status: "missing", zone: entry.zone, expected: entry.sha256 });
725
+ continue;
726
+ }
727
+ const actual = sha256File(resolved);
728
+ if (actual === entry.sha256) {
729
+ files.push({ path: entry.path, status: "match", zone: entry.zone, actual });
730
+ } else {
731
+ files.push({
732
+ path: entry.path,
733
+ status: "modified",
734
+ zone: entry.zone,
735
+ expected: entry.sha256,
736
+ actual
737
+ });
738
+ }
739
+ }
740
+ for (const path of listInstanceFiles(input.cwd)) {
741
+ if (tracked.has(path)) continue;
742
+ files.push({ path, status: "untracked" });
743
+ }
744
+ const counts = {
745
+ match: files.filter((file) => file.status === "match").length,
746
+ modified: files.filter((file) => file.status === "modified").length,
747
+ missing: files.filter((file) => file.status === "missing").length,
748
+ untracked: files.filter((file) => file.status === "untracked").length
749
+ };
750
+ const ok = !files.some(
751
+ (file) => file.zone === "managed" && (file.status === "modified" || file.status === "missing")
752
+ );
753
+ return {
754
+ schemaVersion: 1,
755
+ templateVersion: input.manifest.templateVersion,
756
+ generatedAt: input.manifest.generatedAt,
757
+ manifest: input.origin,
758
+ ok,
759
+ counts,
760
+ files
761
+ };
762
+ }
763
+ function formatTemplateReport(report) {
764
+ const lines = [
765
+ `template version: ${report.templateVersion}`,
766
+ `generated at: ${report.generatedAt}`,
767
+ `match: ${report.counts.match}`,
768
+ `modified: ${report.counts.modified}`,
769
+ `missing: ${report.counts.missing}`,
770
+ `untracked: ${report.counts.untracked}`
771
+ ];
772
+ for (const file of report.files) {
773
+ if (file.status === "match") continue;
774
+ if (file.status === "untracked") {
775
+ lines.push(`untracked: ${file.path}`);
776
+ continue;
777
+ }
778
+ if (file.zone === "managed" && (file.status === "modified" || file.status === "missing")) {
779
+ lines.push(`error: ${file.status} managed ${file.path}`);
780
+ continue;
781
+ }
782
+ lines.push(`${file.status} ${file.zone} ${file.path}`);
783
+ }
784
+ return lines;
785
+ }
786
+ async function runTemplateDoctor(options) {
787
+ const print = options.print ?? (() => {
788
+ });
789
+ const lines = [];
790
+ const say = (line) => {
791
+ lines.push(line);
792
+ print(line);
793
+ };
794
+ try {
795
+ const loaded = await loadManifestText(options.manifest, options.cwd, options.fetch);
796
+ const manifest = parseTemplateManifest(loaded.text);
797
+ const report = classifyTemplate({
798
+ cwd: options.cwd,
799
+ manifest,
800
+ origin: loaded.origin
801
+ });
802
+ if (options.json) {
803
+ say(JSON.stringify(report, null, 2));
804
+ } else {
805
+ for (const line of formatTemplateReport(report)) say(line);
806
+ }
807
+ return { exitCode: report.ok ? 0 : 1, lines, report };
808
+ } catch (error) {
809
+ const message = error instanceof Error ? error.message : String(error);
810
+ return { exitCode: 1, lines, error: message };
811
+ }
812
+ }
813
+
814
+ // src/cli/registry.ts
815
+ var INSTALLED_RECORD_REL = ".umec/installed-registry-items.json";
816
+ var ITEM_ID = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
817
+ var SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
818
+ function isRecord2(value) {
819
+ return typeof value === "object" && value !== null && !Array.isArray(value);
820
+ }
821
+ function isRisk(value) {
822
+ return value === "low" || value === "medium" || value === "high";
823
+ }
824
+ function isRegistryItemId(value) {
825
+ return ITEM_ID.test(value);
826
+ }
827
+ function defaultRegistryRoot(root = findPackageRoot()) {
828
+ return join5(root, "registry");
829
+ }
830
+ function toPosix2(rel) {
831
+ return rel.split(sep2).join("/");
832
+ }
833
+ function stripInlineComment(line) {
834
+ let quote = null;
835
+ for (let i = 0; i < line.length; i += 1) {
836
+ const char = line[i];
837
+ if (quote) {
838
+ if (char === quote && line[i - 1] !== "\\") quote = null;
839
+ continue;
840
+ }
841
+ if (char === '"' || char === "'") {
842
+ quote = char;
843
+ continue;
844
+ }
845
+ if (char === "#") return line.slice(0, i).trimEnd();
846
+ }
847
+ return line;
848
+ }
849
+ function splitTopLevel(input, sep3) {
850
+ const parts = [];
851
+ let buf = "";
852
+ let depthBrace = 0;
853
+ let depthBracket = 0;
854
+ let quote = null;
855
+ for (let i = 0; i < input.length; i += 1) {
856
+ const char = input[i] ?? "";
857
+ if (quote) {
858
+ buf += char;
859
+ if (char === quote && input[i - 1] !== "\\") quote = null;
860
+ continue;
861
+ }
862
+ if (char === '"' || char === "'") {
863
+ quote = char;
864
+ buf += char;
865
+ continue;
866
+ }
867
+ if (char === "{") depthBrace += 1;
868
+ if (char === "}") depthBrace -= 1;
869
+ if (char === "[") depthBracket += 1;
870
+ if (char === "]") depthBracket -= 1;
871
+ if (char === sep3 && depthBrace === 0 && depthBracket === 0) {
872
+ if (buf.trim()) parts.push(buf.trim());
873
+ buf = "";
874
+ continue;
875
+ }
876
+ buf += char;
877
+ }
878
+ if (buf.trim()) parts.push(buf.trim());
879
+ return parts;
880
+ }
881
+ function splitKeyValue(input) {
882
+ let quote = null;
883
+ for (let i = 0; i < input.length; i += 1) {
884
+ const char = input[i];
885
+ if (quote) {
886
+ if (char === quote) quote = null;
887
+ continue;
888
+ }
889
+ if (char === '"' || char === "'") {
890
+ quote = char;
891
+ continue;
892
+ }
893
+ if (char === ":") return [input.slice(0, i).trim(), input.slice(i + 1).trim()];
894
+ }
895
+ return null;
896
+ }
897
+ function unquote(input) {
898
+ if (input.startsWith('"') && input.endsWith('"') && input.length >= 2 || input.startsWith("'") && input.endsWith("'") && input.length >= 2) {
899
+ return input.slice(1, -1);
900
+ }
901
+ return input;
902
+ }
903
+ function parseYamlValue(raw) {
904
+ const value = raw.trim();
905
+ if (value === "") return "";
906
+ if (value === "true") return true;
907
+ if (value === "false") return false;
908
+ if (value === "null") return null;
909
+ if (value.startsWith("{") && value.endsWith("}")) {
910
+ const entries = splitTopLevel(value.slice(1, -1), ",");
911
+ const record = {};
912
+ for (const entry of entries) {
913
+ const kv = splitKeyValue(entry);
914
+ if (!kv) throw new Error(`frontmatter \u306E object \u304C\u4E0D\u6B63\u3067\u3059: ${entry}`);
915
+ record[unquote(kv[0])] = parseYamlValue(kv[1]);
916
+ }
917
+ return record;
918
+ }
919
+ if (value.startsWith("[") && value.endsWith("]")) {
920
+ return splitTopLevel(value.slice(1, -1), ",").map((entry) => parseYamlValue(entry));
921
+ }
922
+ return unquote(value);
923
+ }
924
+ function parseFrontmatter(text) {
925
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(text);
926
+ if (!match) throw new Error("manifest.md \u306B YAML frontmatter \u304C\u3042\u308A\u307E\u305B\u3093\u3002");
927
+ const data = {};
928
+ for (const rawLine of match[1].split(/\r?\n/)) {
929
+ const line = stripInlineComment(rawLine).trim();
930
+ if (!line) continue;
931
+ const kv = splitKeyValue(line);
932
+ if (!kv || !kv[0]) throw new Error(`frontmatter \u306E\u884C\u304C\u4E0D\u6B63\u3067\u3059: ${rawLine}`);
933
+ data[kv[0]] = parseYamlValue(kv[1]);
934
+ }
935
+ return { data, body: match[2] };
936
+ }
937
+ function parseStringArray(value, field) {
938
+ if (value === void 0) return [];
939
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.length === 0)) {
940
+ throw new Error(`${field} \u306F\u6587\u5B57\u5217\u306E\u914D\u5217\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`);
941
+ }
942
+ return value;
943
+ }
944
+ function parseRequires(value) {
945
+ if (value === void 0) return {};
946
+ if (!isRecord2(value)) throw new Error("requires \u306F object \u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002");
947
+ const requires = {};
948
+ for (const key of Object.keys(value)) {
949
+ if (key !== "core" && key !== "admin" && key !== "items") {
950
+ throw new Error(`requires.${key} \u306B\u306F\u672A\u5BFE\u5FDC\u3067\u3059\uFF08\u5BFE\u5FDC: core, admin, items\uFF09\u3002`);
951
+ }
952
+ }
953
+ if (value.core !== void 0) {
954
+ if (typeof value.core !== "string" || value.core.length === 0) {
955
+ throw new Error("requires.core \u306F\u30D0\u30FC\u30B8\u30E7\u30F3\u7BC4\u56F2\u306E\u6587\u5B57\u5217\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002");
956
+ }
957
+ requires.core = value.core;
958
+ }
959
+ if (value.admin !== void 0) {
960
+ if (typeof value.admin !== "string" || value.admin.length === 0) {
961
+ throw new Error("requires.admin \u306F\u30D0\u30FC\u30B8\u30E7\u30F3\u7BC4\u56F2\u306E\u6587\u5B57\u5217\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002");
962
+ }
963
+ requires.admin = value.admin;
964
+ }
965
+ if (value.items !== void 0) {
966
+ requires.items = parseStringArray(value.items, "requires.items");
967
+ for (const id of requires.items) {
968
+ if (!isRegistryItemId(id)) throw new Error(`requires.items \u306E id \u304C\u4E0D\u6B63\u3067\u3059: ${id}`);
969
+ }
970
+ }
971
+ return requires;
972
+ }
973
+ function parseSemVer(input) {
974
+ const match = SEMVER.exec(input.trim());
975
+ if (!match) return null;
976
+ return {
977
+ major: Number(match[1]),
978
+ minor: Number(match[2]),
979
+ patch: Number(match[3]),
980
+ prerelease: match[4] ? match[4].split(".") : []
981
+ };
982
+ }
983
+ function compareIdentifier(a, b) {
984
+ const aNum = /^\d+$/.test(a);
985
+ const bNum = /^\d+$/.test(b);
986
+ if (aNum && bNum) return Number(a) - Number(b);
987
+ if (aNum !== bNum) return aNum ? -1 : 1;
988
+ return a < b ? -1 : a > b ? 1 : 0;
989
+ }
990
+ function compareSemVer(a, b) {
991
+ if (a.major !== b.major) return a.major - b.major;
992
+ if (a.minor !== b.minor) return a.minor - b.minor;
993
+ if (a.patch !== b.patch) return a.patch - b.patch;
994
+ if (a.prerelease.length === 0 && b.prerelease.length === 0) return 0;
995
+ if (a.prerelease.length === 0) return 1;
996
+ if (b.prerelease.length === 0) return -1;
997
+ const n = Math.max(a.prerelease.length, b.prerelease.length);
998
+ for (let i = 0; i < n; i += 1) {
999
+ const left = a.prerelease[i];
1000
+ const right = b.prerelease[i];
1001
+ if (left === void 0) return -1;
1002
+ if (right === void 0) return 1;
1003
+ const cmp = compareIdentifier(left, right);
1004
+ if (cmp !== 0) return cmp;
1005
+ }
1006
+ return 0;
1007
+ }
1008
+ function satisfiesVersionRange(version, range) {
1009
+ const parsed = parseSemVer(version);
1010
+ if (!parsed) throw new Error(`\u30D0\u30FC\u30B8\u30E7\u30F3\u304C\u4E0D\u6B63\u3067\u3059: ${version}`);
1011
+ const trimmed = range.trim();
1012
+ const operators = [">=", "<=", ">", "<", "="];
1013
+ const operator = operators.find((item) => trimmed.startsWith(item));
1014
+ const spec = operator ? trimmed.slice(operator.length).trim() : trimmed;
1015
+ const target = parseSemVer(spec);
1016
+ if (!target) throw new Error(`\u30D0\u30FC\u30B8\u30E7\u30F3\u7BC4\u56F2\u304C\u4E0D\u6B63\u3067\u3059: ${range}`);
1017
+ const cmp = compareSemVer(parsed, target);
1018
+ if (operator === ">=") return cmp >= 0;
1019
+ if (operator === ">") return cmp > 0;
1020
+ if (operator === "<=") return cmp <= 0;
1021
+ if (operator === "<") return cmp < 0;
1022
+ return cmp === 0;
1023
+ }
1024
+ function titleFromBody(body, fallback) {
1025
+ const heading = /^#\s+(.+)$/m.exec(body);
1026
+ const title = heading?.[1]?.trim();
1027
+ return title && title.length > 0 ? title : fallback;
1028
+ }
1029
+ function listFilesRecursive(root) {
1030
+ const out = [];
1031
+ const walk = (dir) => {
1032
+ for (const entry of readdirSync3(dir, { withFileTypes: true })) {
1033
+ if (entry.name === ".DS_Store" || entry.name.startsWith(".")) continue;
1034
+ const full = join5(dir, entry.name);
1035
+ if (entry.isDirectory()) {
1036
+ walk(full);
1037
+ continue;
1038
+ }
1039
+ if (!entry.isFile()) continue;
1040
+ const rel = toPosix2(relative2(root, full));
1041
+ if (!isSafeRelPath(rel)) throw new Error(`registry \u306E path \u304C\u4E0D\u6B63\u3067\u3059: ${rel}`);
1042
+ out.push(rel);
1043
+ }
1044
+ };
1045
+ walk(root);
1046
+ out.sort();
1047
+ return out;
1048
+ }
1049
+ function loadItemFiles(itemDir) {
1050
+ const filesRoot = join5(itemDir, "files");
1051
+ if (!existsSync4(filesRoot) || !statSync2(filesRoot).isDirectory()) {
1052
+ throw new Error("files/ \u304C\u3042\u308A\u307E\u305B\u3093\u3002");
1053
+ }
1054
+ const paths = listFilesRecursive(filesRoot);
1055
+ if (paths.length === 0) throw new Error("files/ \u304C\u7A7A\u3067\u3059\u3002");
1056
+ return paths.map((path) => {
1057
+ const sourcePath = join5(filesRoot, ...path.split("/"));
1058
+ return { path, sourcePath, sha256: sha256File(sourcePath) };
1059
+ });
1060
+ }
1061
+ function parseRegistryManifest(text) {
1062
+ const { data, body } = parseFrontmatter(text);
1063
+ if (typeof data.id !== "string" || !isRegistryItemId(data.id)) {
1064
+ throw new Error(`manifest.md \u306E id \u304C\u4E0D\u6B63\u3067\u3059: ${String(data.id)}`);
1065
+ }
1066
+ if (typeof data.version !== "string" || !parseSemVer(data.version)) {
1067
+ throw new Error(`manifest.md \u306E version \u304C\u4E0D\u6B63\u3067\u3059: ${String(data.version)}`);
1068
+ }
1069
+ if (!isRisk(data.risk)) {
1070
+ throw new Error(`manifest.md \u306E risk \u304C\u4E0D\u6B63\u3067\u3059: ${String(data.risk)}`);
1071
+ }
1072
+ return {
1073
+ id: data.id,
1074
+ version: data.version,
1075
+ title: titleFromBody(body, data.id),
1076
+ risk: data.risk,
1077
+ requires: parseRequires(data.requires),
1078
+ touches: parseStringArray(data.touches, "touches")
1079
+ };
1080
+ }
1081
+ function loadRegistryItem(registryRoot, itemId) {
1082
+ if (!isRegistryItemId(itemId)) {
1083
+ throw new Error(`registry item id \u304C\u4E0D\u6B63\u3067\u3059: ${itemId}`);
1084
+ }
1085
+ const dir = resolve2(registryRoot, itemId);
1086
+ const rel = relative2(resolve2(registryRoot), dir);
1087
+ if (!rel || rel === ".." || rel.startsWith(`..${sep2}`) || isAbsolute2(rel) || rel.includes(sep2)) {
1088
+ throw new Error(`registry item id \u304C\u4E0D\u6B63\u3067\u3059: ${itemId}`);
1089
+ }
1090
+ const manifestPath = join5(dir, "manifest.md");
1091
+ if (!existsSync4(dir) || !statSync2(dir).isDirectory() || !existsSync4(manifestPath) || !statSync2(manifestPath).isFile()) {
1092
+ throw new Error(`registry item \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${itemId}`);
1093
+ }
1094
+ const parsed = parseRegistryManifest(readFileSync5(manifestPath, "utf8"));
1095
+ if (parsed.id !== itemId) {
1096
+ throw new Error(`manifest.md \u306E id (${parsed.id}) \u304C\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u540D (${itemId}) \u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002`);
1097
+ }
1098
+ return {
1099
+ ...parsed,
1100
+ files: loadItemFiles(dir),
1101
+ dir,
1102
+ manifestPath
1103
+ };
1104
+ }
1105
+ function listRegistryItems(registryRoot) {
1106
+ if (!existsSync4(registryRoot)) return [];
1107
+ if (!statSync2(registryRoot).isDirectory()) {
1108
+ throw new Error(`registry \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304C\u4E0D\u6B63\u3067\u3059: ${registryRoot}`);
1109
+ }
1110
+ const ids = readdirSync3(registryRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && existsSync4(join5(registryRoot, entry.name, "manifest.md"))).map((entry) => entry.name).sort();
1111
+ return ids.map((id) => loadRegistryItem(registryRoot, id));
1112
+ }
1113
+ function catalogEntry(item) {
1114
+ return {
1115
+ id: item.id,
1116
+ version: item.version,
1117
+ title: item.title,
1118
+ risk: item.risk,
1119
+ requires: item.requires,
1120
+ touches: item.touches
1121
+ };
1122
+ }
1123
+ function formatRequires(requires) {
1124
+ const parts = [];
1125
+ if (requires.core) parts.push(`core ${requires.core}`);
1126
+ if (requires.admin) parts.push(`admin ${requires.admin}`);
1127
+ if (requires.items?.length) parts.push(`items ${requires.items.join(", ")}`);
1128
+ return parts.length > 0 ? parts.join("; ") : "(none)";
1129
+ }
1130
+ function sayResult(print, lines, line) {
1131
+ lines.push(line);
1132
+ print(line);
1133
+ }
1134
+ function finishJson(print, lines, payload, exitCode) {
1135
+ sayResult(print, lines, JSON.stringify(payload, null, 2));
1136
+ return { exitCode, lines };
1137
+ }
1138
+ function finishError(options, error, extra = {}) {
1139
+ if (options.json) return finishJson(options.print, options.lines, { ok: false, error, ...extra }, 1);
1140
+ return { exitCode: 1, lines: options.lines, error };
1141
+ }
1142
+ function readCustomerPackageVersion(cwd, packageName) {
1143
+ const path = join5(cwd, "node_modules", ...packageName.split("/"), "package.json");
1144
+ if (!existsSync4(path) || !statSync2(path).isFile()) return null;
1145
+ try {
1146
+ const pkg = JSON.parse(readFileSync5(path, "utf8"));
1147
+ return typeof pkg.version === "string" ? pkg.version : null;
1148
+ } catch {
1149
+ return null;
1150
+ }
1151
+ }
1152
+ function readInstalledRecord(cwd) {
1153
+ const path = resolveInstancePath(cwd, INSTALLED_RECORD_REL);
1154
+ if (!path || !existsSync4(path) || !statSync2(path).isFile()) {
1155
+ return { schemaVersion: 1, items: [] };
1156
+ }
1157
+ let parsed;
1158
+ try {
1159
+ parsed = JSON.parse(readFileSync5(path, "utf8"));
1160
+ } catch {
1161
+ throw new Error(`${INSTALLED_RECORD_REL} \u306E JSON \u304C\u4E0D\u6B63\u3067\u3059\u3002`);
1162
+ }
1163
+ if (!isRecord2(parsed) || parsed.schemaVersion !== 1 || !Array.isArray(parsed.items)) {
1164
+ throw new Error(`${INSTALLED_RECORD_REL} \u306E schemaVersion \u306F 1 \u306E\u307F\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059\u3002`);
1165
+ }
1166
+ const items = [];
1167
+ for (const entry of parsed.items) {
1168
+ if (!isRecord2(entry)) throw new Error(`${INSTALLED_RECORD_REL} \u306E items \u304C\u4E0D\u6B63\u3067\u3059\u3002`);
1169
+ if (typeof entry.id !== "string" || !isRegistryItemId(entry.id)) {
1170
+ throw new Error(`${INSTALLED_RECORD_REL} \u306E id \u304C\u4E0D\u6B63\u3067\u3059: ${String(entry.id)}`);
1171
+ }
1172
+ if (typeof entry.version !== "string" || !parseSemVer(entry.version)) {
1173
+ throw new Error(`${INSTALLED_RECORD_REL} \u306E version \u304C\u4E0D\u6B63\u3067\u3059: ${entry.id}`);
1174
+ }
1175
+ if (typeof entry.appliedAt !== "string" || entry.appliedAt.length === 0) {
1176
+ throw new Error(`${INSTALLED_RECORD_REL} \u306E appliedAt \u304C\u4E0D\u6B63\u3067\u3059: ${entry.id}`);
1177
+ }
1178
+ items.push({ id: entry.id, version: entry.version, appliedAt: entry.appliedAt });
1179
+ }
1180
+ return { schemaVersion: 1, items };
1181
+ }
1182
+ function writeInstalledRecord(cwd, record) {
1183
+ const path = resolveInstancePath(cwd, INSTALLED_RECORD_REL);
1184
+ if (!path) throw new Error(`${INSTALLED_RECORD_REL} \u3092\u66F8\u304D\u8FBC\u3081\u307E\u305B\u3093\u3002`);
1185
+ mkdirSync2(dirname2(path), { recursive: true });
1186
+ writeFileSync2(path, `${JSON.stringify(record, null, 2)}
1187
+ `);
1188
+ return INSTALLED_RECORD_REL;
1189
+ }
1190
+ function upsertInstalledItem(record, item) {
1191
+ const items = record.items.slice();
1192
+ const index = items.findIndex((entry) => entry.id === item.id);
1193
+ if (index >= 0) items[index] = item;
1194
+ else items.push(item);
1195
+ return { schemaVersion: 1, items };
1196
+ }
1197
+ function classifyRegistryFiles(cwd, files) {
1198
+ return files.map((file) => {
1199
+ const resolved = resolveInstancePath(cwd, file.path);
1200
+ if (!resolved || !existsSync4(resolved) || !statSync2(resolved).isFile()) {
1201
+ return { path: file.path, status: "missing", expected: file.sha256 };
1202
+ }
1203
+ const actual = sha256File(resolved);
1204
+ if (actual === file.sha256) return { path: file.path, status: "match", actual };
1205
+ return { path: file.path, status: "modified", expected: file.sha256, actual };
1206
+ });
1207
+ }
1208
+ function unmetRequires(input) {
1209
+ const messages = [];
1210
+ const { item } = input;
1211
+ if (item.requires.core) {
1212
+ try {
1213
+ if (!satisfiesVersionRange(input.coreVersion, item.requires.core)) {
1214
+ messages.push(
1215
+ `@umec/core ${item.requires.core} \u304C\u5FC5\u8981\u3067\u3059\uFF08\u73FE\u5728 ${input.coreVersion}\uFF09\u3002pnpm update @umec/core \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
1216
+ );
1217
+ }
1218
+ } catch (error) {
1219
+ messages.push(error instanceof Error ? error.message : String(error));
1220
+ }
1221
+ }
1222
+ if (item.requires.admin) {
1223
+ if (!input.adminVersion) {
1224
+ messages.push(
1225
+ `@umec/admin ${item.requires.admin} \u304C\u5FC5\u8981\u3067\u3059\u3002\u5148\u306B @umec/admin \u3092\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
1226
+ );
1227
+ } else {
1228
+ try {
1229
+ if (!satisfiesVersionRange(input.adminVersion, item.requires.admin)) {
1230
+ messages.push(
1231
+ `@umec/admin ${item.requires.admin} \u304C\u5FC5\u8981\u3067\u3059\uFF08\u73FE\u5728 ${input.adminVersion}\uFF09\u3002`
1232
+ );
1233
+ }
1234
+ } catch (error) {
1235
+ messages.push(error instanceof Error ? error.message : String(error));
1236
+ }
1237
+ }
1238
+ }
1239
+ for (const requiredId of item.requires.items ?? []) {
1240
+ if (!input.installed.items.some((entry) => entry.id === requiredId)) {
1241
+ messages.push(`\u5148\u306B umec add ${requiredId} \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
1242
+ }
1243
+ }
1244
+ return messages;
1245
+ }
1246
+ async function runRegistryList(options) {
1247
+ const print = options.print ?? (() => {
1248
+ });
1249
+ const lines = [];
1250
+ try {
1251
+ const registryRoot = options.registryRoot ?? defaultRegistryRoot();
1252
+ const items = listRegistryItems(registryRoot).map(catalogEntry);
1253
+ if (options.json) {
1254
+ return finishJson(print, lines, { schemaVersion: 1, items }, 0);
1255
+ }
1256
+ if (items.length === 0) {
1257
+ sayResult(print, lines, "(no registry items)");
1258
+ return { exitCode: 0, lines };
1259
+ }
1260
+ for (const item of items) {
1261
+ sayResult(print, lines, `${item.id} ${item.version} ${item.risk}`);
1262
+ sayResult(print, lines, ` ${item.title}`);
1263
+ sayResult(print, lines, ` requires: ${formatRequires(item.requires)}`);
1264
+ }
1265
+ return { exitCode: 0, lines };
1266
+ } catch (error) {
1267
+ const message = error instanceof Error ? error.message : String(error);
1268
+ return finishError({ json: options.json, print, lines }, message);
1269
+ }
1270
+ }
1271
+ async function runRegistryAdd(options) {
1272
+ const print = options.print ?? (() => {
1273
+ });
1274
+ const lines = [];
1275
+ const itemId = options.itemId;
1276
+ if (!itemId) {
1277
+ return finishError({ json: options.json, print, lines }, "umec add \u306B\u306F <item-id> \u307E\u305F\u306F --list \u304C\u5FC5\u8981\u3067\u3059\u3002");
1278
+ }
1279
+ try {
1280
+ const registryRoot = options.registryRoot ?? defaultRegistryRoot();
1281
+ const item = loadRegistryItem(registryRoot, itemId);
1282
+ const installed = readInstalledRecord(options.cwd);
1283
+ const coreVersion = options.coreVersion ?? coreVersionFromPackage();
1284
+ const adminVersion = options.adminVersion !== void 0 ? options.adminVersion : readCustomerPackageVersion(options.cwd, "@umec/admin");
1285
+ const unmet = unmetRequires({ item, coreVersion, adminVersion, installed });
1286
+ if (unmet.length > 0) {
1287
+ return finishError(
1288
+ { json: options.json, print, lines },
1289
+ unmet.join("\n"),
1290
+ { id: item.id, version: item.version, unmet }
1291
+ );
1292
+ }
1293
+ const findings = classifyRegistryFiles(options.cwd, item.files);
1294
+ const modified = findings.filter((file) => file.status === "modified");
1295
+ if (modified.length > 0 && options.force !== true) {
1296
+ const error = `\u6539\u5909\u6E08\u307F\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308B\u305F\u3081 ${item.id} \u3092\u66F8\u304D\u8FBC\u307F\u307E\u305B\u3093\u3002umec diff ${item.id} \u3067\u5DEE\u5206\u3092\u78BA\u8A8D\u3057\u3001\u4E0A\u66F8\u304D\u3059\u308B\u5834\u5408\u306F umec add ${item.id} --force \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`;
1297
+ if (options.json) {
1298
+ return finishJson(
1299
+ print,
1300
+ lines,
1301
+ { ok: false, id: item.id, version: item.version, error, files: findings },
1302
+ 1
1303
+ );
1304
+ }
1305
+ for (const file of modified) sayResult(print, lines, `modified: ${file.path}`);
1306
+ return { exitCode: 1, lines, error };
1307
+ }
1308
+ const written = [];
1309
+ const unchanged = [];
1310
+ for (const file of item.files) {
1311
+ const dest = resolveInstancePath(options.cwd, file.path);
1312
+ if (!dest) throw new Error(`\u66F8\u304D\u8FBC\u307F\u5148\u304C\u4E0D\u6B63\u3067\u3059: ${file.path}`);
1313
+ if (existsSync4(dest) && !statSync2(dest).isFile()) {
1314
+ throw new Error(`${file.path} \u306F\u30D5\u30A1\u30A4\u30EB\u3067\u306F\u306A\u3044\u305F\u3081\u66F8\u304D\u8FBC\u3081\u307E\u305B\u3093\u3002`);
1315
+ }
1316
+ const finding = findings.find((entry) => entry.path === file.path);
1317
+ if (finding?.status === "match") {
1318
+ unchanged.push(file.path);
1319
+ continue;
1320
+ }
1321
+ mkdirSync2(dirname2(dest), { recursive: true });
1322
+ writeFileSync2(dest, readFileSync5(file.sourcePath));
1323
+ written.push(file.path);
1324
+ }
1325
+ const appliedAt = new Date(options.now ?? Date.now()).toISOString();
1326
+ const record = upsertInstalledItem(installed, { id: item.id, version: item.version, appliedAt });
1327
+ writeInstalledRecord(options.cwd, record);
1328
+ if (options.json) {
1329
+ return finishJson(
1330
+ print,
1331
+ lines,
1332
+ {
1333
+ ok: true,
1334
+ id: item.id,
1335
+ version: item.version,
1336
+ written,
1337
+ unchanged,
1338
+ record: INSTALLED_RECORD_REL,
1339
+ appliedAt
1340
+ },
1341
+ 0
1342
+ );
1343
+ }
1344
+ sayResult(print, lines, `applied ${item.id}@${item.version}`);
1345
+ for (const path of written) sayResult(print, lines, `wrote ${path}`);
1346
+ for (const path of unchanged) sayResult(print, lines, `unchanged ${path}`);
1347
+ sayResult(print, lines, `recorded ${INSTALLED_RECORD_REL}`);
1348
+ return { exitCode: 0, lines };
1349
+ } catch (error) {
1350
+ const message = error instanceof Error ? error.message : String(error);
1351
+ return finishError({ json: options.json, print, lines }, message, itemId ? { id: itemId } : {});
1352
+ }
1353
+ }
1354
+ function formatDiffReport(item, files) {
1355
+ const counts = {
1356
+ match: files.filter((file) => file.status === "match").length,
1357
+ modified: files.filter((file) => file.status === "modified").length,
1358
+ missing: files.filter((file) => file.status === "missing").length
1359
+ };
1360
+ const lines = [
1361
+ `registry item: ${item.id} ${item.version}`,
1362
+ `match: ${counts.match}`,
1363
+ `modified: ${counts.modified}`,
1364
+ `missing: ${counts.missing}`
1365
+ ];
1366
+ for (const file of files) {
1367
+ if (file.status === "match") continue;
1368
+ lines.push(`${file.status}: ${file.path}`);
1369
+ }
1370
+ return lines;
1371
+ }
1372
+ async function runRegistryDiff(options) {
1373
+ const print = options.print ?? (() => {
1374
+ });
1375
+ const lines = [];
1376
+ const itemId = options.itemId;
1377
+ if (!itemId) {
1378
+ return finishError({ json: options.json, print, lines }, "umec diff \u306B\u306F <item-id> \u304C\u5FC5\u8981\u3067\u3059\u3002");
1379
+ }
1380
+ try {
1381
+ const registryRoot = options.registryRoot ?? defaultRegistryRoot();
1382
+ const item = loadRegistryItem(registryRoot, itemId);
1383
+ const files = classifyRegistryFiles(options.cwd, item.files);
1384
+ const ok = files.every((file) => file.status === "match");
1385
+ const counts = {
1386
+ match: files.filter((file) => file.status === "match").length,
1387
+ modified: files.filter((file) => file.status === "modified").length,
1388
+ missing: files.filter((file) => file.status === "missing").length
1389
+ };
1390
+ if (options.json) {
1391
+ return finishJson(
1392
+ print,
1393
+ lines,
1394
+ {
1395
+ schemaVersion: 1,
1396
+ ok,
1397
+ id: item.id,
1398
+ version: item.version,
1399
+ title: item.title,
1400
+ counts,
1401
+ files
1402
+ },
1403
+ ok ? 0 : 1
1404
+ );
1405
+ }
1406
+ for (const line of formatDiffReport(item, files)) sayResult(print, lines, line);
1407
+ return { exitCode: ok ? 0 : 1, lines };
1408
+ } catch (error) {
1409
+ const message = error instanceof Error ? error.message : String(error);
1410
+ return finishError({ json: options.json, print, lines }, message, { id: itemId });
1411
+ }
1412
+ }
1413
+
584
1414
  // src/cli/wrangler.ts
585
1415
  import { spawn } from "child_process";
586
- import { readFileSync as readFileSync4 } from "fs";
587
- import { join as join4 } from "path";
1416
+ import { readFileSync as readFileSync6 } from "fs";
1417
+ import { join as join6 } from "path";
588
1418
 
589
1419
  // src/cli/executor.ts
590
1420
  function sqlLiteral(value) {
@@ -604,7 +1434,7 @@ function bindSql(sql, params = []) {
604
1434
 
605
1435
  // src/cli/wrangler.ts
606
1436
  function defaultRunCommand(command, args, cwd) {
607
- return new Promise((resolve, reject) => {
1437
+ return new Promise((resolve3, reject) => {
608
1438
  const child = spawn(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
609
1439
  let stdout = "";
610
1440
  let stderr = "";
@@ -616,7 +1446,7 @@ function defaultRunCommand(command, args, cwd) {
616
1446
  });
617
1447
  child.on("error", reject);
618
1448
  child.on("close", (code) => {
619
- resolve({ code: code ?? 1, stdout, stderr });
1449
+ resolve3({ code: code ?? 1, stdout, stderr });
620
1450
  });
621
1451
  });
622
1452
  }
@@ -626,9 +1456,9 @@ function parseJsonc(text) {
626
1456
  }
627
1457
  function readD1DatabaseName(cwd, override) {
628
1458
  if (override) return override;
629
- const path = join4(cwd, "wrangler.jsonc");
1459
+ const path = join6(cwd, "wrangler.jsonc");
630
1460
  try {
631
- const parsed = parseJsonc(readFileSync4(path, "utf8"));
1461
+ const parsed = parseJsonc(readFileSync6(path, "utf8"));
632
1462
  return parsed.d1_databases?.[0]?.database_name ?? null;
633
1463
  } catch {
634
1464
  return null;
@@ -654,7 +1484,7 @@ function createWranglerExecutor(options) {
654
1484
  const execute = async (sql) => {
655
1485
  const result = await runCommand(
656
1486
  "wrangler",
657
- ["d1", "execute", options.database, location, "--json", "--command", sql],
1487
+ ["d1", "execute", options.database, location, "--json", `--command=${sql}`],
658
1488
  options.cwd
659
1489
  );
660
1490
  if (result.code !== 0) {
@@ -702,8 +1532,21 @@ function createWranglerExecutor(options) {
702
1532
  }
703
1533
 
704
1534
  // src/cli/index.ts
1535
+ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
1536
+ "dry-run",
1537
+ "adopt",
1538
+ "repair",
1539
+ "remote",
1540
+ "local",
1541
+ "template",
1542
+ "json",
1543
+ "help",
1544
+ "force",
1545
+ "list"
1546
+ ]);
705
1547
  function parseArgs(argv) {
706
1548
  const flags = {};
1549
+ const positionals = [];
707
1550
  let command = null;
708
1551
  for (let i = 0; i < argv.length; i += 1) {
709
1552
  const arg = argv[i];
@@ -716,7 +1559,7 @@ function parseArgs(argv) {
716
1559
  }
717
1560
  const name = arg.slice(2);
718
1561
  const next = argv[i + 1];
719
- if (next && !next.startsWith("--") && name !== "dry-run" && name !== "adopt" && name !== "repair" && name !== "remote" && name !== "local") {
1562
+ if (next && !next.startsWith("--") && !BOOLEAN_FLAGS.has(name)) {
720
1563
  flags[name] = next;
721
1564
  i += 1;
722
1565
  } else {
@@ -725,24 +1568,83 @@ function parseArgs(argv) {
725
1568
  continue;
726
1569
  }
727
1570
  if (!command) command = arg;
1571
+ else positionals.push(arg);
728
1572
  }
729
- return { command, flags };
1573
+ return { command, flags, positionals };
730
1574
  }
731
1575
  function usage() {
732
1576
  return `Usage:
733
1577
  umec migrate [--dry-run] [--local|--remote] [--adopt] [--repair] [--database NAME]
734
1578
  umec doctor [--local|--remote] [--database NAME]
1579
+ umec doctor --template --manifest <path-or-url> [--json]
1580
+ umec add --list [--json]
1581
+ umec add <item-id> [--force] [--json]
1582
+ umec diff <item-id> [--json]
735
1583
  umec extend [--key NAME] [--type boolean|string|number]`;
736
1584
  }
737
1585
  async function runCli(argv, io = {}) {
738
1586
  const cwd = io.cwd ?? process.cwd();
739
1587
  const stdout = io.stdout ?? ((message) => console.log(message));
740
1588
  const stderr = io.stderr ?? ((message) => console.error(message));
741
- const { command, flags } = parseArgs(argv);
1589
+ const { command, flags, positionals } = parseArgs(argv);
742
1590
  if (!command || command === "help" || flags.help) {
743
1591
  stdout(usage());
744
1592
  return command ? 0 : 1;
745
1593
  }
1594
+ if (command === "add" || command === "diff") {
1595
+ if (command === "add" && flags.list === true) {
1596
+ if (positionals.length > 0) {
1597
+ stderr("umec add --list \u306F item-id \u3092\u53D6\u308A\u307E\u305B\u3093\u3002");
1598
+ return 1;
1599
+ }
1600
+ const listed = await runRegistryList({
1601
+ cwd,
1602
+ json: flags.json === true,
1603
+ print: stdout,
1604
+ registryRoot: io.registryRoot
1605
+ });
1606
+ if (listed.error) stderr(listed.error);
1607
+ return listed.exitCode;
1608
+ }
1609
+ if (positionals.length > 1) {
1610
+ stderr(`umec ${command} \u306F item-id \u30921\u3064\u3060\u3051\u53D7\u3051\u53D6\u308A\u307E\u3059\u3002`);
1611
+ return 1;
1612
+ }
1613
+ const result = command === "add" ? await runRegistryAdd({
1614
+ cwd,
1615
+ itemId: positionals[0],
1616
+ force: flags.force === true,
1617
+ json: flags.json === true,
1618
+ print: stdout,
1619
+ registryRoot: io.registryRoot,
1620
+ coreVersion: io.coreVersion,
1621
+ adminVersion: io.adminVersion,
1622
+ now: io.now
1623
+ }) : await runRegistryDiff({
1624
+ cwd,
1625
+ itemId: positionals[0],
1626
+ json: flags.json === true,
1627
+ print: stdout,
1628
+ registryRoot: io.registryRoot
1629
+ });
1630
+ if (result.error) stderr(result.error);
1631
+ return result.exitCode;
1632
+ }
1633
+ if (command === "doctor" && flags.template === true) {
1634
+ const manifest = flags.manifest;
1635
+ if (typeof manifest !== "string" || manifest.length === 0) {
1636
+ stderr("umec doctor --template \u306B\u306F --manifest <path-or-url> \u304C\u5FC5\u8981\u3067\u3059\u3002");
1637
+ return 1;
1638
+ }
1639
+ const result = await runTemplateDoctor({
1640
+ cwd,
1641
+ manifest,
1642
+ json: flags.json === true,
1643
+ print: stdout
1644
+ });
1645
+ if (result.error) stderr(result.error);
1646
+ return result.exitCode;
1647
+ }
746
1648
  const snapshot = loadSnapshotJson();
747
1649
  const remote = flags.remote === true;
748
1650
  const database = typeof flags.database === "string" ? flags.database : readD1DatabaseName(cwd);
@@ -800,10 +1702,10 @@ async function runCli(argv, io = {}) {
800
1702
  function isDirectRun() {
801
1703
  const invoked = process.argv[1];
802
1704
  if (!invoked) return false;
803
- return /(?:^|[\\/])cli(?:\.js)?$/.test(invoked) || invoked.includes(`${join5("dist", "cli")}`);
1705
+ return /(?:^|[\\/])cli(?:\.js)?$/.test(invoked) || invoked.includes(`${join7("dist", "cli")}`);
804
1706
  }
805
1707
  if (isDirectRun()) {
806
- const pkg = JSON.parse(readFileSync5(join5(findPackageRoot(), "package.json"), "utf8"));
1708
+ const pkg = JSON.parse(readFileSync7(join7(findPackageRoot(), "package.json"), "utf8"));
807
1709
  if (process.argv.includes("--version") || process.argv.includes("-V")) {
808
1710
  console.log(pkg.version);
809
1711
  process.exit(0);