@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 +925 -23
- 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";
|
|
@@ -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
|
|
512
|
-
if (
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
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
|
|
587
|
-
import { join as
|
|
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((
|
|
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
|
-
|
|
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 =
|
|
1459
|
+
const path = join6(cwd, "wrangler.jsonc");
|
|
630
1460
|
try {
|
|
631
|
-
const parsed = parseJsonc(
|
|
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",
|
|
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
|
|
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(`${
|
|
1705
|
+
return /(?:^|[\\/])cli(?:\.js)?$/.test(invoked) || invoked.includes(`${join7("dist", "cli")}`);
|
|
804
1706
|
}
|
|
805
1707
|
if (isDirectRun()) {
|
|
806
|
-
const pkg = JSON.parse(
|
|
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);
|