@umec/core 0.1.0-alpha.11 → 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 +915 -14
- package/dist/cli.js.map +1 -1
- package/package.json +3 -2
- package/registry/disclosure/files/migrations/local/0001_disclosure_requests.sql +23 -0
- package/registry/disclosure/files/src/lib/disclosure-db.ts +214 -0
- package/registry/disclosure/files/src/lib/disclosure-request.ts +818 -0
- package/registry/disclosure/files/src/lib/disclosure-send.ts +231 -0
- package/registry/disclosure/files/src/pages/admin/disclosure.astro +269 -0
- package/registry/disclosure/files/src/pages/api/admin/disclosure-send.ts +11 -0
- package/registry/disclosure/files/src/pages/api/disclosure-request.ts +16 -0
- package/registry/disclosure/files/src/pages/legal/disclosure.astro +135 -0
- package/registry/disclosure/manifest.md +242 -0
- package/registry/example-hello/files/src/example-hello.txt +1 -0
- package/registry/example-hello/manifest.md +10 -0
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
|
|
18
|
-
import { join as
|
|
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";
|
|
@@ -582,10 +582,839 @@ function coreVersionFromPackage(root = findPackageRoot()) {
|
|
|
582
582
|
return pkg.version;
|
|
583
583
|
}
|
|
584
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
|
+
|
|
585
1414
|
// src/cli/wrangler.ts
|
|
586
1415
|
import { spawn } from "child_process";
|
|
587
|
-
import { readFileSync as
|
|
588
|
-
import { join as
|
|
1416
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
1417
|
+
import { join as join6 } from "path";
|
|
589
1418
|
|
|
590
1419
|
// src/cli/executor.ts
|
|
591
1420
|
function sqlLiteral(value) {
|
|
@@ -605,7 +1434,7 @@ function bindSql(sql, params = []) {
|
|
|
605
1434
|
|
|
606
1435
|
// src/cli/wrangler.ts
|
|
607
1436
|
function defaultRunCommand(command, args, cwd) {
|
|
608
|
-
return new Promise((
|
|
1437
|
+
return new Promise((resolve3, reject) => {
|
|
609
1438
|
const child = spawn(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
610
1439
|
let stdout = "";
|
|
611
1440
|
let stderr = "";
|
|
@@ -617,7 +1446,7 @@ function defaultRunCommand(command, args, cwd) {
|
|
|
617
1446
|
});
|
|
618
1447
|
child.on("error", reject);
|
|
619
1448
|
child.on("close", (code) => {
|
|
620
|
-
|
|
1449
|
+
resolve3({ code: code ?? 1, stdout, stderr });
|
|
621
1450
|
});
|
|
622
1451
|
});
|
|
623
1452
|
}
|
|
@@ -627,9 +1456,9 @@ function parseJsonc(text) {
|
|
|
627
1456
|
}
|
|
628
1457
|
function readD1DatabaseName(cwd, override) {
|
|
629
1458
|
if (override) return override;
|
|
630
|
-
const path =
|
|
1459
|
+
const path = join6(cwd, "wrangler.jsonc");
|
|
631
1460
|
try {
|
|
632
|
-
const parsed = parseJsonc(
|
|
1461
|
+
const parsed = parseJsonc(readFileSync6(path, "utf8"));
|
|
633
1462
|
return parsed.d1_databases?.[0]?.database_name ?? null;
|
|
634
1463
|
} catch {
|
|
635
1464
|
return null;
|
|
@@ -655,7 +1484,7 @@ function createWranglerExecutor(options) {
|
|
|
655
1484
|
const execute = async (sql) => {
|
|
656
1485
|
const result = await runCommand(
|
|
657
1486
|
"wrangler",
|
|
658
|
-
["d1", "execute", options.database, location, "--json",
|
|
1487
|
+
["d1", "execute", options.database, location, "--json", `--command=${sql}`],
|
|
659
1488
|
options.cwd
|
|
660
1489
|
);
|
|
661
1490
|
if (result.code !== 0) {
|
|
@@ -703,8 +1532,21 @@ function createWranglerExecutor(options) {
|
|
|
703
1532
|
}
|
|
704
1533
|
|
|
705
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
|
+
]);
|
|
706
1547
|
function parseArgs(argv) {
|
|
707
1548
|
const flags = {};
|
|
1549
|
+
const positionals = [];
|
|
708
1550
|
let command = null;
|
|
709
1551
|
for (let i = 0; i < argv.length; i += 1) {
|
|
710
1552
|
const arg = argv[i];
|
|
@@ -717,7 +1559,7 @@ function parseArgs(argv) {
|
|
|
717
1559
|
}
|
|
718
1560
|
const name = arg.slice(2);
|
|
719
1561
|
const next = argv[i + 1];
|
|
720
|
-
if (next && !next.startsWith("--") && name
|
|
1562
|
+
if (next && !next.startsWith("--") && !BOOLEAN_FLAGS.has(name)) {
|
|
721
1563
|
flags[name] = next;
|
|
722
1564
|
i += 1;
|
|
723
1565
|
} else {
|
|
@@ -726,24 +1568,83 @@ function parseArgs(argv) {
|
|
|
726
1568
|
continue;
|
|
727
1569
|
}
|
|
728
1570
|
if (!command) command = arg;
|
|
1571
|
+
else positionals.push(arg);
|
|
729
1572
|
}
|
|
730
|
-
return { command, flags };
|
|
1573
|
+
return { command, flags, positionals };
|
|
731
1574
|
}
|
|
732
1575
|
function usage() {
|
|
733
1576
|
return `Usage:
|
|
734
1577
|
umec migrate [--dry-run] [--local|--remote] [--adopt] [--repair] [--database NAME]
|
|
735
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]
|
|
736
1583
|
umec extend [--key NAME] [--type boolean|string|number]`;
|
|
737
1584
|
}
|
|
738
1585
|
async function runCli(argv, io = {}) {
|
|
739
1586
|
const cwd = io.cwd ?? process.cwd();
|
|
740
1587
|
const stdout = io.stdout ?? ((message) => console.log(message));
|
|
741
1588
|
const stderr = io.stderr ?? ((message) => console.error(message));
|
|
742
|
-
const { command, flags } = parseArgs(argv);
|
|
1589
|
+
const { command, flags, positionals } = parseArgs(argv);
|
|
743
1590
|
if (!command || command === "help" || flags.help) {
|
|
744
1591
|
stdout(usage());
|
|
745
1592
|
return command ? 0 : 1;
|
|
746
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
|
+
}
|
|
747
1648
|
const snapshot = loadSnapshotJson();
|
|
748
1649
|
const remote = flags.remote === true;
|
|
749
1650
|
const database = typeof flags.database === "string" ? flags.database : readD1DatabaseName(cwd);
|
|
@@ -801,10 +1702,10 @@ async function runCli(argv, io = {}) {
|
|
|
801
1702
|
function isDirectRun() {
|
|
802
1703
|
const invoked = process.argv[1];
|
|
803
1704
|
if (!invoked) return false;
|
|
804
|
-
return /(?:^|[\\/])cli(?:\.js)?$/.test(invoked) || invoked.includes(`${
|
|
1705
|
+
return /(?:^|[\\/])cli(?:\.js)?$/.test(invoked) || invoked.includes(`${join7("dist", "cli")}`);
|
|
805
1706
|
}
|
|
806
1707
|
if (isDirectRun()) {
|
|
807
|
-
const pkg = JSON.parse(
|
|
1708
|
+
const pkg = JSON.parse(readFileSync7(join7(findPackageRoot(), "package.json"), "utf8"));
|
|
808
1709
|
if (process.argv.includes("--version") || process.argv.includes("-V")) {
|
|
809
1710
|
console.log(pkg.version);
|
|
810
1711
|
process.exit(0);
|