@valbuild/cli 0.85.0 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/dist/valbuild-cli-cli.cjs.dev.js +144 -42
- package/cli/dist/valbuild-cli-cli.cjs.prod.js +144 -42
- package/cli/dist/valbuild-cli-cli.esm.js +144 -42
- package/package.json +4 -4
- package/src/cli.ts +24 -14
- package/src/connect.ts +126 -0
- package/src/{files.ts → listUnusedFiles.ts} +15 -29
- package/src/login.ts +10 -7
|
@@ -13,6 +13,7 @@ var ts = require('typescript');
|
|
|
13
13
|
var z = require('zod');
|
|
14
14
|
var node_module = require('node:module');
|
|
15
15
|
var fs$1 = require('fs');
|
|
16
|
+
var child_process = require('child_process');
|
|
16
17
|
|
|
17
18
|
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
|
|
18
19
|
|
|
@@ -486,19 +487,18 @@ function findSimilar(key, targets) {
|
|
|
486
487
|
})).sort((a, b) => a.distance - b.distance);
|
|
487
488
|
}
|
|
488
489
|
|
|
489
|
-
async function
|
|
490
|
-
root
|
|
491
|
-
managedDir
|
|
490
|
+
async function listUnusedFiles({
|
|
491
|
+
root
|
|
492
492
|
}) {
|
|
493
|
-
const
|
|
493
|
+
const managedDir = "public/val";
|
|
494
494
|
const projectRoot = root ? path__default["default"].resolve(root) : process.cwd();
|
|
495
495
|
const service = await server.createService(projectRoot, {});
|
|
496
496
|
const valFiles = await fastGlob.glob("**/*.val.{js,ts}", {
|
|
497
497
|
ignore: ["node_modules/**"],
|
|
498
498
|
cwd: projectRoot
|
|
499
499
|
});
|
|
500
|
-
const
|
|
501
|
-
async function
|
|
500
|
+
const filesUsedByVal = [];
|
|
501
|
+
async function pushFilesUsedByVal(file) {
|
|
502
502
|
const moduleId = `/${file}`; // TODO: check if this always works? (Windows?)
|
|
503
503
|
const valModule = await service.get(moduleId, "", {
|
|
504
504
|
validate: true,
|
|
@@ -515,11 +515,7 @@ async function files({
|
|
|
515
515
|
const value = error.value;
|
|
516
516
|
if (isFileRef(value)) {
|
|
517
517
|
const absoluteFilePathUsedByVal = path__default["default"].join(projectRoot, ...value[core.FILE_REF_PROP].split("/"));
|
|
518
|
-
|
|
519
|
-
console.log(absoluteFilePathUsedByVal);
|
|
520
|
-
} else {
|
|
521
|
-
absoluteFilesPathUsedByVal.push(absoluteFilePathUsedByVal);
|
|
522
|
-
}
|
|
518
|
+
filesUsedByVal.push(absoluteFilePathUsedByVal);
|
|
523
519
|
}
|
|
524
520
|
}
|
|
525
521
|
}
|
|
@@ -527,19 +523,17 @@ async function files({
|
|
|
527
523
|
}
|
|
528
524
|
}
|
|
529
525
|
for (const file of valFiles) {
|
|
530
|
-
await
|
|
526
|
+
await pushFilesUsedByVal(file);
|
|
531
527
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
console.log(path__default["default"].join(managedRoot, file));
|
|
542
|
-
}
|
|
528
|
+
const managedRoot = path__default["default"].join(projectRoot, managedDir);
|
|
529
|
+
const allFilesInManagedDir = await fastGlob.glob("**/*", {
|
|
530
|
+
ignore: ["node_modules/**"],
|
|
531
|
+
cwd: managedRoot
|
|
532
|
+
});
|
|
533
|
+
for (const file of allFilesInManagedDir) {
|
|
534
|
+
const absoluteFilePath = path__default["default"].join(managedRoot, file);
|
|
535
|
+
if (!filesUsedByVal.includes(absoluteFilePath)) {
|
|
536
|
+
console.log(path__default["default"].join(managedRoot, file));
|
|
543
537
|
}
|
|
544
538
|
}
|
|
545
539
|
service.dispose();
|
|
@@ -581,7 +575,106 @@ const getVersions = () => {
|
|
|
581
575
|
};
|
|
582
576
|
};
|
|
583
577
|
|
|
584
|
-
const host = process.env.VAL_BUILD_URL || "https://
|
|
578
|
+
const host$1 = process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
579
|
+
async function connect(options) {
|
|
580
|
+
const {
|
|
581
|
+
root
|
|
582
|
+
} = options;
|
|
583
|
+
const projectRoot = root ? path__default["default"].resolve(root) : process.cwd();
|
|
584
|
+
const maybeProject = await tryGetProject(projectRoot);
|
|
585
|
+
const maybeGitRemote = await tryGetGitRemote(projectRoot);
|
|
586
|
+
const maybeGitOwnerAndRepo = maybeGitRemote !== null ? getGitHubOwnerAndRepo(maybeGitRemote) : null;
|
|
587
|
+
const params = new URLSearchParams();
|
|
588
|
+
if (maybeProject) {
|
|
589
|
+
params.set("org", maybeProject.orgName);
|
|
590
|
+
params.set("project", maybeProject.projectName);
|
|
591
|
+
}
|
|
592
|
+
if (maybeGitOwnerAndRepo) {
|
|
593
|
+
params.set("github_repo", [maybeGitOwnerAndRepo.owner, maybeGitOwnerAndRepo.repo].join("/"));
|
|
594
|
+
}
|
|
595
|
+
const url = `${host$1}/connect?${params.toString()}`;
|
|
596
|
+
|
|
597
|
+
// Open url in default browser and show fallback instructions:
|
|
598
|
+
console.log(picocolors__default["default"].cyan("\nStarting connect process in browser...\n"));
|
|
599
|
+
console.log(picocolors__default["default"].dim(`\nIf the browser does not open, please visit:\n${url}\n`));
|
|
600
|
+
if (process.platform === "win32") {
|
|
601
|
+
// Windows
|
|
602
|
+
child_process.exec(`start ${url}`);
|
|
603
|
+
} else if (process.platform === "darwin") {
|
|
604
|
+
// macOS
|
|
605
|
+
child_process.exec(`open ${url}`);
|
|
606
|
+
} else {
|
|
607
|
+
// Linux and others
|
|
608
|
+
child_process.exec(`xdg-open ${url}`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
async function tryGetProject(projectRoot) {
|
|
612
|
+
const valConfigFile = (await evalValConfigFile(projectRoot, "val.config.ts")) || (await evalValConfigFile(projectRoot, "val.config.js"));
|
|
613
|
+
if (valConfigFile && valConfigFile.project) {
|
|
614
|
+
const parts = valConfigFile.project.split("/");
|
|
615
|
+
if (parts.length === 2) {
|
|
616
|
+
return {
|
|
617
|
+
orgName: parts[0],
|
|
618
|
+
projectName: parts[1]
|
|
619
|
+
};
|
|
620
|
+
} else {
|
|
621
|
+
console.error(picocolors__default["default"].red(`Invalid project format in val.config file: "${valConfigFile.project}". Expected format "orgName/projectName".`));
|
|
622
|
+
process.exit(1);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
function getGitHubOwnerAndRepo(gitRemote) {
|
|
628
|
+
const sshMatch = gitRemote.match(/git@github\.com:(.+?)\/(.+?)(\.git)?$/);
|
|
629
|
+
if (sshMatch) {
|
|
630
|
+
return {
|
|
631
|
+
owner: sshMatch[1],
|
|
632
|
+
repo: sshMatch[2]
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
const httpsMatch = gitRemote.match(/https:\/\/github\.com\/(.+?)\/(.+?)(\.git)?$/);
|
|
636
|
+
if (httpsMatch) {
|
|
637
|
+
return {
|
|
638
|
+
owner: httpsMatch[1],
|
|
639
|
+
repo: httpsMatch[2]
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
async function tryGetGitRemote(root) {
|
|
645
|
+
try {
|
|
646
|
+
const gitConfig = tryGetGitConfig(root);
|
|
647
|
+
if (!gitConfig) {
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
const remoteMatch = gitConfig.match(/\[remote "origin"\][\s\S]*?url = (.+)/);
|
|
651
|
+
if (remoteMatch && remoteMatch[1]) {
|
|
652
|
+
return remoteMatch[1].trim();
|
|
653
|
+
}
|
|
654
|
+
return null;
|
|
655
|
+
} catch (error) {
|
|
656
|
+
console.error(picocolors__default["default"].red("Failed to read .git/config file."), error);
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function tryGetGitConfig(root) {
|
|
661
|
+
let currentDir = root;
|
|
662
|
+
let lastDir = null;
|
|
663
|
+
while (currentDir !== lastDir) {
|
|
664
|
+
const gitConfigPath = path__default["default"].join(currentDir, ".git", "config");
|
|
665
|
+
if (fs__default$1["default"].existsSync(gitConfigPath)) {
|
|
666
|
+
return fs__default$1["default"].readFileSync(gitConfigPath, "utf-8");
|
|
667
|
+
}
|
|
668
|
+
lastDir = currentDir;
|
|
669
|
+
currentDir = path__default["default"].dirname(currentDir);
|
|
670
|
+
if (lastDir === currentDir) {
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const host = process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
585
678
|
async function login(options) {
|
|
586
679
|
try {
|
|
587
680
|
var _response$headers$get;
|
|
@@ -598,7 +691,7 @@ async function login(options) {
|
|
|
598
691
|
let url;
|
|
599
692
|
if (!((_response$headers$get = response.headers.get("content-type")) !== null && _response$headers$get !== void 0 && _response$headers$get.includes("application/json"))) {
|
|
600
693
|
const text = await response.text();
|
|
601
|
-
console.error(picocolors__default["default"].red("Unexpected failure while trying to login (content type was not JSON). Server response:
|
|
694
|
+
console.error(picocolors__default["default"].red("Unexpected failure while trying to login (content type was not JSON). "), text ? `Server response: ${text} (status: ${response.status})` : `Status: ${response.status}`);
|
|
602
695
|
process.exit(1);
|
|
603
696
|
}
|
|
604
697
|
const json = await response.json();
|
|
@@ -630,7 +723,9 @@ async function pollForConfirmation(token) {
|
|
|
630
723
|
const start = Date.now();
|
|
631
724
|
while (Date.now() - start < MAX_DURATION) {
|
|
632
725
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
633
|
-
const response = await fetch(`${host}/api/login?token=${token}&consume=true
|
|
726
|
+
const response = await fetch(`${host}/api/login?token=${token}&consume=true`, {
|
|
727
|
+
method: "POST"
|
|
728
|
+
});
|
|
634
729
|
if (response.status === 500) {
|
|
635
730
|
console.error(picocolors__default["default"].red("An error occurred on the server."));
|
|
636
731
|
process.exit(1);
|
|
@@ -638,7 +733,7 @@ async function pollForConfirmation(token) {
|
|
|
638
733
|
if (response.status === 200) {
|
|
639
734
|
const json = await response.json();
|
|
640
735
|
if (json) {
|
|
641
|
-
if (typeof json.profile.
|
|
736
|
+
if (typeof json.profile.email === "string" && typeof json.pat === "string") {
|
|
642
737
|
return json;
|
|
643
738
|
} else {
|
|
644
739
|
console.error(picocolors__default["default"].red("Unexpected response from the server."));
|
|
@@ -655,7 +750,7 @@ function saveToken(result, filePath) {
|
|
|
655
750
|
recursive: true
|
|
656
751
|
});
|
|
657
752
|
fs__default$1["default"].writeFileSync(filePath, JSON.stringify(result, null, 2));
|
|
658
|
-
console.log(picocolors__default["default"].green(`Token for ${picocolors__default["default"].cyan(result.profile.
|
|
753
|
+
console.log(picocolors__default["default"].green(`Token for ${picocolors__default["default"].cyan(result.profile.email)} saved to ${picocolors__default["default"].cyan(filePath)}`));
|
|
659
754
|
}
|
|
660
755
|
|
|
661
756
|
async function main() {
|
|
@@ -673,7 +768,8 @@ async function main() {
|
|
|
673
768
|
Commands:
|
|
674
769
|
validate
|
|
675
770
|
login
|
|
676
|
-
|
|
771
|
+
files
|
|
772
|
+
connect
|
|
677
773
|
versions
|
|
678
774
|
|
|
679
775
|
Command: validate
|
|
@@ -684,21 +780,21 @@ async function main() {
|
|
|
684
780
|
|
|
685
781
|
|
|
686
782
|
Command: login
|
|
687
|
-
Description: login to
|
|
783
|
+
Description: login to admin.val.build and generate a Personal Access Token
|
|
688
784
|
Options:
|
|
689
785
|
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
690
786
|
|
|
691
787
|
|
|
692
|
-
Command:
|
|
693
|
-
Description:
|
|
694
|
-
|
|
695
|
-
|
|
788
|
+
Command: connect
|
|
789
|
+
Description: connect your local project to a Val Build project at admin.val.build
|
|
790
|
+
Options:
|
|
791
|
+
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
696
792
|
|
|
697
|
-
|
|
698
|
-
|
|
793
|
+
Command: list-unused-files
|
|
794
|
+
Description: EXPERIMENTAL.
|
|
795
|
+
List files that are in public/val but not in use by any Val module.
|
|
699
796
|
This is useful for cleaning up unused files.
|
|
700
797
|
Options:
|
|
701
|
-
--managedDir [dir] If set, list files found in directory that are not managed by Val
|
|
702
798
|
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
703
799
|
`, {
|
|
704
800
|
flags: {
|
|
@@ -731,13 +827,12 @@ async function main() {
|
|
|
731
827
|
}
|
|
732
828
|
const [command] = input;
|
|
733
829
|
switch (command) {
|
|
734
|
-
case "files":
|
|
830
|
+
case "list-unused-files":
|
|
735
831
|
if (flags.fix || flags.noEslint) {
|
|
736
|
-
return error(`Command "files" does not support --fix or --noEslint flags`);
|
|
832
|
+
return error(`Command "list-unused-files" does not support --fix or --noEslint flags`);
|
|
737
833
|
}
|
|
738
|
-
return
|
|
739
|
-
root: flags.root
|
|
740
|
-
managedDir: flags.managedDir
|
|
834
|
+
return listUnusedFiles({
|
|
835
|
+
root: flags.root
|
|
741
836
|
});
|
|
742
837
|
case "versions":
|
|
743
838
|
return versions();
|
|
@@ -745,6 +840,13 @@ async function main() {
|
|
|
745
840
|
return login({
|
|
746
841
|
root: flags.root
|
|
747
842
|
});
|
|
843
|
+
case "connect":
|
|
844
|
+
if (flags.fix || flags.noEslint) {
|
|
845
|
+
return error(`Command "connect" does not support --fix or --noEslint flags`);
|
|
846
|
+
}
|
|
847
|
+
return connect({
|
|
848
|
+
root: flags.root
|
|
849
|
+
});
|
|
748
850
|
case "validate":
|
|
749
851
|
case "idate":
|
|
750
852
|
if (flags.managedDir) {
|
|
@@ -13,6 +13,7 @@ var ts = require('typescript');
|
|
|
13
13
|
var z = require('zod');
|
|
14
14
|
var node_module = require('node:module');
|
|
15
15
|
var fs$1 = require('fs');
|
|
16
|
+
var child_process = require('child_process');
|
|
16
17
|
|
|
17
18
|
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
|
|
18
19
|
|
|
@@ -486,19 +487,18 @@ function findSimilar(key, targets) {
|
|
|
486
487
|
})).sort((a, b) => a.distance - b.distance);
|
|
487
488
|
}
|
|
488
489
|
|
|
489
|
-
async function
|
|
490
|
-
root
|
|
491
|
-
managedDir
|
|
490
|
+
async function listUnusedFiles({
|
|
491
|
+
root
|
|
492
492
|
}) {
|
|
493
|
-
const
|
|
493
|
+
const managedDir = "public/val";
|
|
494
494
|
const projectRoot = root ? path__default["default"].resolve(root) : process.cwd();
|
|
495
495
|
const service = await server.createService(projectRoot, {});
|
|
496
496
|
const valFiles = await fastGlob.glob("**/*.val.{js,ts}", {
|
|
497
497
|
ignore: ["node_modules/**"],
|
|
498
498
|
cwd: projectRoot
|
|
499
499
|
});
|
|
500
|
-
const
|
|
501
|
-
async function
|
|
500
|
+
const filesUsedByVal = [];
|
|
501
|
+
async function pushFilesUsedByVal(file) {
|
|
502
502
|
const moduleId = `/${file}`; // TODO: check if this always works? (Windows?)
|
|
503
503
|
const valModule = await service.get(moduleId, "", {
|
|
504
504
|
validate: true,
|
|
@@ -515,11 +515,7 @@ async function files({
|
|
|
515
515
|
const value = error.value;
|
|
516
516
|
if (isFileRef(value)) {
|
|
517
517
|
const absoluteFilePathUsedByVal = path__default["default"].join(projectRoot, ...value[core.FILE_REF_PROP].split("/"));
|
|
518
|
-
|
|
519
|
-
console.log(absoluteFilePathUsedByVal);
|
|
520
|
-
} else {
|
|
521
|
-
absoluteFilesPathUsedByVal.push(absoluteFilePathUsedByVal);
|
|
522
|
-
}
|
|
518
|
+
filesUsedByVal.push(absoluteFilePathUsedByVal);
|
|
523
519
|
}
|
|
524
520
|
}
|
|
525
521
|
}
|
|
@@ -527,19 +523,17 @@ async function files({
|
|
|
527
523
|
}
|
|
528
524
|
}
|
|
529
525
|
for (const file of valFiles) {
|
|
530
|
-
await
|
|
526
|
+
await pushFilesUsedByVal(file);
|
|
531
527
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
console.log(path__default["default"].join(managedRoot, file));
|
|
542
|
-
}
|
|
528
|
+
const managedRoot = path__default["default"].join(projectRoot, managedDir);
|
|
529
|
+
const allFilesInManagedDir = await fastGlob.glob("**/*", {
|
|
530
|
+
ignore: ["node_modules/**"],
|
|
531
|
+
cwd: managedRoot
|
|
532
|
+
});
|
|
533
|
+
for (const file of allFilesInManagedDir) {
|
|
534
|
+
const absoluteFilePath = path__default["default"].join(managedRoot, file);
|
|
535
|
+
if (!filesUsedByVal.includes(absoluteFilePath)) {
|
|
536
|
+
console.log(path__default["default"].join(managedRoot, file));
|
|
543
537
|
}
|
|
544
538
|
}
|
|
545
539
|
service.dispose();
|
|
@@ -581,7 +575,106 @@ const getVersions = () => {
|
|
|
581
575
|
};
|
|
582
576
|
};
|
|
583
577
|
|
|
584
|
-
const host = process.env.VAL_BUILD_URL || "https://
|
|
578
|
+
const host$1 = process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
579
|
+
async function connect(options) {
|
|
580
|
+
const {
|
|
581
|
+
root
|
|
582
|
+
} = options;
|
|
583
|
+
const projectRoot = root ? path__default["default"].resolve(root) : process.cwd();
|
|
584
|
+
const maybeProject = await tryGetProject(projectRoot);
|
|
585
|
+
const maybeGitRemote = await tryGetGitRemote(projectRoot);
|
|
586
|
+
const maybeGitOwnerAndRepo = maybeGitRemote !== null ? getGitHubOwnerAndRepo(maybeGitRemote) : null;
|
|
587
|
+
const params = new URLSearchParams();
|
|
588
|
+
if (maybeProject) {
|
|
589
|
+
params.set("org", maybeProject.orgName);
|
|
590
|
+
params.set("project", maybeProject.projectName);
|
|
591
|
+
}
|
|
592
|
+
if (maybeGitOwnerAndRepo) {
|
|
593
|
+
params.set("github_repo", [maybeGitOwnerAndRepo.owner, maybeGitOwnerAndRepo.repo].join("/"));
|
|
594
|
+
}
|
|
595
|
+
const url = `${host$1}/connect?${params.toString()}`;
|
|
596
|
+
|
|
597
|
+
// Open url in default browser and show fallback instructions:
|
|
598
|
+
console.log(picocolors__default["default"].cyan("\nStarting connect process in browser...\n"));
|
|
599
|
+
console.log(picocolors__default["default"].dim(`\nIf the browser does not open, please visit:\n${url}\n`));
|
|
600
|
+
if (process.platform === "win32") {
|
|
601
|
+
// Windows
|
|
602
|
+
child_process.exec(`start ${url}`);
|
|
603
|
+
} else if (process.platform === "darwin") {
|
|
604
|
+
// macOS
|
|
605
|
+
child_process.exec(`open ${url}`);
|
|
606
|
+
} else {
|
|
607
|
+
// Linux and others
|
|
608
|
+
child_process.exec(`xdg-open ${url}`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
async function tryGetProject(projectRoot) {
|
|
612
|
+
const valConfigFile = (await evalValConfigFile(projectRoot, "val.config.ts")) || (await evalValConfigFile(projectRoot, "val.config.js"));
|
|
613
|
+
if (valConfigFile && valConfigFile.project) {
|
|
614
|
+
const parts = valConfigFile.project.split("/");
|
|
615
|
+
if (parts.length === 2) {
|
|
616
|
+
return {
|
|
617
|
+
orgName: parts[0],
|
|
618
|
+
projectName: parts[1]
|
|
619
|
+
};
|
|
620
|
+
} else {
|
|
621
|
+
console.error(picocolors__default["default"].red(`Invalid project format in val.config file: "${valConfigFile.project}". Expected format "orgName/projectName".`));
|
|
622
|
+
process.exit(1);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
function getGitHubOwnerAndRepo(gitRemote) {
|
|
628
|
+
const sshMatch = gitRemote.match(/git@github\.com:(.+?)\/(.+?)(\.git)?$/);
|
|
629
|
+
if (sshMatch) {
|
|
630
|
+
return {
|
|
631
|
+
owner: sshMatch[1],
|
|
632
|
+
repo: sshMatch[2]
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
const httpsMatch = gitRemote.match(/https:\/\/github\.com\/(.+?)\/(.+?)(\.git)?$/);
|
|
636
|
+
if (httpsMatch) {
|
|
637
|
+
return {
|
|
638
|
+
owner: httpsMatch[1],
|
|
639
|
+
repo: httpsMatch[2]
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
async function tryGetGitRemote(root) {
|
|
645
|
+
try {
|
|
646
|
+
const gitConfig = tryGetGitConfig(root);
|
|
647
|
+
if (!gitConfig) {
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
const remoteMatch = gitConfig.match(/\[remote "origin"\][\s\S]*?url = (.+)/);
|
|
651
|
+
if (remoteMatch && remoteMatch[1]) {
|
|
652
|
+
return remoteMatch[1].trim();
|
|
653
|
+
}
|
|
654
|
+
return null;
|
|
655
|
+
} catch (error) {
|
|
656
|
+
console.error(picocolors__default["default"].red("Failed to read .git/config file."), error);
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function tryGetGitConfig(root) {
|
|
661
|
+
let currentDir = root;
|
|
662
|
+
let lastDir = null;
|
|
663
|
+
while (currentDir !== lastDir) {
|
|
664
|
+
const gitConfigPath = path__default["default"].join(currentDir, ".git", "config");
|
|
665
|
+
if (fs__default$1["default"].existsSync(gitConfigPath)) {
|
|
666
|
+
return fs__default$1["default"].readFileSync(gitConfigPath, "utf-8");
|
|
667
|
+
}
|
|
668
|
+
lastDir = currentDir;
|
|
669
|
+
currentDir = path__default["default"].dirname(currentDir);
|
|
670
|
+
if (lastDir === currentDir) {
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const host = process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
585
678
|
async function login(options) {
|
|
586
679
|
try {
|
|
587
680
|
var _response$headers$get;
|
|
@@ -598,7 +691,7 @@ async function login(options) {
|
|
|
598
691
|
let url;
|
|
599
692
|
if (!((_response$headers$get = response.headers.get("content-type")) !== null && _response$headers$get !== void 0 && _response$headers$get.includes("application/json"))) {
|
|
600
693
|
const text = await response.text();
|
|
601
|
-
console.error(picocolors__default["default"].red("Unexpected failure while trying to login (content type was not JSON). Server response:
|
|
694
|
+
console.error(picocolors__default["default"].red("Unexpected failure while trying to login (content type was not JSON). "), text ? `Server response: ${text} (status: ${response.status})` : `Status: ${response.status}`);
|
|
602
695
|
process.exit(1);
|
|
603
696
|
}
|
|
604
697
|
const json = await response.json();
|
|
@@ -630,7 +723,9 @@ async function pollForConfirmation(token) {
|
|
|
630
723
|
const start = Date.now();
|
|
631
724
|
while (Date.now() - start < MAX_DURATION) {
|
|
632
725
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
633
|
-
const response = await fetch(`${host}/api/login?token=${token}&consume=true
|
|
726
|
+
const response = await fetch(`${host}/api/login?token=${token}&consume=true`, {
|
|
727
|
+
method: "POST"
|
|
728
|
+
});
|
|
634
729
|
if (response.status === 500) {
|
|
635
730
|
console.error(picocolors__default["default"].red("An error occurred on the server."));
|
|
636
731
|
process.exit(1);
|
|
@@ -638,7 +733,7 @@ async function pollForConfirmation(token) {
|
|
|
638
733
|
if (response.status === 200) {
|
|
639
734
|
const json = await response.json();
|
|
640
735
|
if (json) {
|
|
641
|
-
if (typeof json.profile.
|
|
736
|
+
if (typeof json.profile.email === "string" && typeof json.pat === "string") {
|
|
642
737
|
return json;
|
|
643
738
|
} else {
|
|
644
739
|
console.error(picocolors__default["default"].red("Unexpected response from the server."));
|
|
@@ -655,7 +750,7 @@ function saveToken(result, filePath) {
|
|
|
655
750
|
recursive: true
|
|
656
751
|
});
|
|
657
752
|
fs__default$1["default"].writeFileSync(filePath, JSON.stringify(result, null, 2));
|
|
658
|
-
console.log(picocolors__default["default"].green(`Token for ${picocolors__default["default"].cyan(result.profile.
|
|
753
|
+
console.log(picocolors__default["default"].green(`Token for ${picocolors__default["default"].cyan(result.profile.email)} saved to ${picocolors__default["default"].cyan(filePath)}`));
|
|
659
754
|
}
|
|
660
755
|
|
|
661
756
|
async function main() {
|
|
@@ -673,7 +768,8 @@ async function main() {
|
|
|
673
768
|
Commands:
|
|
674
769
|
validate
|
|
675
770
|
login
|
|
676
|
-
|
|
771
|
+
files
|
|
772
|
+
connect
|
|
677
773
|
versions
|
|
678
774
|
|
|
679
775
|
Command: validate
|
|
@@ -684,21 +780,21 @@ async function main() {
|
|
|
684
780
|
|
|
685
781
|
|
|
686
782
|
Command: login
|
|
687
|
-
Description: login to
|
|
783
|
+
Description: login to admin.val.build and generate a Personal Access Token
|
|
688
784
|
Options:
|
|
689
785
|
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
690
786
|
|
|
691
787
|
|
|
692
|
-
Command:
|
|
693
|
-
Description:
|
|
694
|
-
|
|
695
|
-
|
|
788
|
+
Command: connect
|
|
789
|
+
Description: connect your local project to a Val Build project at admin.val.build
|
|
790
|
+
Options:
|
|
791
|
+
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
696
792
|
|
|
697
|
-
|
|
698
|
-
|
|
793
|
+
Command: list-unused-files
|
|
794
|
+
Description: EXPERIMENTAL.
|
|
795
|
+
List files that are in public/val but not in use by any Val module.
|
|
699
796
|
This is useful for cleaning up unused files.
|
|
700
797
|
Options:
|
|
701
|
-
--managedDir [dir] If set, list files found in directory that are not managed by Val
|
|
702
798
|
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
703
799
|
`, {
|
|
704
800
|
flags: {
|
|
@@ -731,13 +827,12 @@ async function main() {
|
|
|
731
827
|
}
|
|
732
828
|
const [command] = input;
|
|
733
829
|
switch (command) {
|
|
734
|
-
case "files":
|
|
830
|
+
case "list-unused-files":
|
|
735
831
|
if (flags.fix || flags.noEslint) {
|
|
736
|
-
return error(`Command "files" does not support --fix or --noEslint flags`);
|
|
832
|
+
return error(`Command "list-unused-files" does not support --fix or --noEslint flags`);
|
|
737
833
|
}
|
|
738
|
-
return
|
|
739
|
-
root: flags.root
|
|
740
|
-
managedDir: flags.managedDir
|
|
834
|
+
return listUnusedFiles({
|
|
835
|
+
root: flags.root
|
|
741
836
|
});
|
|
742
837
|
case "versions":
|
|
743
838
|
return versions();
|
|
@@ -745,6 +840,13 @@ async function main() {
|
|
|
745
840
|
return login({
|
|
746
841
|
root: flags.root
|
|
747
842
|
});
|
|
843
|
+
case "connect":
|
|
844
|
+
if (flags.fix || flags.noEslint) {
|
|
845
|
+
return error(`Command "connect" does not support --fix or --noEslint flags`);
|
|
846
|
+
}
|
|
847
|
+
return connect({
|
|
848
|
+
root: flags.root
|
|
849
|
+
});
|
|
748
850
|
case "validate":
|
|
749
851
|
case "idate":
|
|
750
852
|
if (flags.managedDir) {
|
|
@@ -11,6 +11,7 @@ import ts from 'typescript';
|
|
|
11
11
|
import z from 'zod';
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
13
|
import fs$1 from 'fs';
|
|
14
|
+
import { exec } from 'child_process';
|
|
14
15
|
|
|
15
16
|
function error(message) {
|
|
16
17
|
console.error(chalk.red("❌Error: ") + message);
|
|
@@ -454,19 +455,18 @@ function findSimilar(key, targets) {
|
|
|
454
455
|
})).sort((a, b) => a.distance - b.distance);
|
|
455
456
|
}
|
|
456
457
|
|
|
457
|
-
async function
|
|
458
|
-
root
|
|
459
|
-
managedDir
|
|
458
|
+
async function listUnusedFiles({
|
|
459
|
+
root
|
|
460
460
|
}) {
|
|
461
|
-
const
|
|
461
|
+
const managedDir = "public/val";
|
|
462
462
|
const projectRoot = root ? path.resolve(root) : process.cwd();
|
|
463
463
|
const service = await createService(projectRoot, {});
|
|
464
464
|
const valFiles = await glob("**/*.val.{js,ts}", {
|
|
465
465
|
ignore: ["node_modules/**"],
|
|
466
466
|
cwd: projectRoot
|
|
467
467
|
});
|
|
468
|
-
const
|
|
469
|
-
async function
|
|
468
|
+
const filesUsedByVal = [];
|
|
469
|
+
async function pushFilesUsedByVal(file) {
|
|
470
470
|
const moduleId = `/${file}`; // TODO: check if this always works? (Windows?)
|
|
471
471
|
const valModule = await service.get(moduleId, "", {
|
|
472
472
|
validate: true,
|
|
@@ -483,11 +483,7 @@ async function files({
|
|
|
483
483
|
const value = error.value;
|
|
484
484
|
if (isFileRef(value)) {
|
|
485
485
|
const absoluteFilePathUsedByVal = path.join(projectRoot, ...value[FILE_REF_PROP].split("/"));
|
|
486
|
-
|
|
487
|
-
console.log(absoluteFilePathUsedByVal);
|
|
488
|
-
} else {
|
|
489
|
-
absoluteFilesPathUsedByVal.push(absoluteFilePathUsedByVal);
|
|
490
|
-
}
|
|
486
|
+
filesUsedByVal.push(absoluteFilePathUsedByVal);
|
|
491
487
|
}
|
|
492
488
|
}
|
|
493
489
|
}
|
|
@@ -495,19 +491,17 @@ async function files({
|
|
|
495
491
|
}
|
|
496
492
|
}
|
|
497
493
|
for (const file of valFiles) {
|
|
498
|
-
await
|
|
494
|
+
await pushFilesUsedByVal(file);
|
|
499
495
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
console.log(path.join(managedRoot, file));
|
|
510
|
-
}
|
|
496
|
+
const managedRoot = path.join(projectRoot, managedDir);
|
|
497
|
+
const allFilesInManagedDir = await glob("**/*", {
|
|
498
|
+
ignore: ["node_modules/**"],
|
|
499
|
+
cwd: managedRoot
|
|
500
|
+
});
|
|
501
|
+
for (const file of allFilesInManagedDir) {
|
|
502
|
+
const absoluteFilePath = path.join(managedRoot, file);
|
|
503
|
+
if (!filesUsedByVal.includes(absoluteFilePath)) {
|
|
504
|
+
console.log(path.join(managedRoot, file));
|
|
511
505
|
}
|
|
512
506
|
}
|
|
513
507
|
service.dispose();
|
|
@@ -549,7 +543,106 @@ const getVersions = () => {
|
|
|
549
543
|
};
|
|
550
544
|
};
|
|
551
545
|
|
|
552
|
-
const host = process.env.VAL_BUILD_URL || "https://
|
|
546
|
+
const host$1 = process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
547
|
+
async function connect(options) {
|
|
548
|
+
const {
|
|
549
|
+
root
|
|
550
|
+
} = options;
|
|
551
|
+
const projectRoot = root ? path.resolve(root) : process.cwd();
|
|
552
|
+
const maybeProject = await tryGetProject(projectRoot);
|
|
553
|
+
const maybeGitRemote = await tryGetGitRemote(projectRoot);
|
|
554
|
+
const maybeGitOwnerAndRepo = maybeGitRemote !== null ? getGitHubOwnerAndRepo(maybeGitRemote) : null;
|
|
555
|
+
const params = new URLSearchParams();
|
|
556
|
+
if (maybeProject) {
|
|
557
|
+
params.set("org", maybeProject.orgName);
|
|
558
|
+
params.set("project", maybeProject.projectName);
|
|
559
|
+
}
|
|
560
|
+
if (maybeGitOwnerAndRepo) {
|
|
561
|
+
params.set("github_repo", [maybeGitOwnerAndRepo.owner, maybeGitOwnerAndRepo.repo].join("/"));
|
|
562
|
+
}
|
|
563
|
+
const url = `${host$1}/connect?${params.toString()}`;
|
|
564
|
+
|
|
565
|
+
// Open url in default browser and show fallback instructions:
|
|
566
|
+
console.log(picocolors.cyan("\nStarting connect process in browser...\n"));
|
|
567
|
+
console.log(picocolors.dim(`\nIf the browser does not open, please visit:\n${url}\n`));
|
|
568
|
+
if (process.platform === "win32") {
|
|
569
|
+
// Windows
|
|
570
|
+
exec(`start ${url}`);
|
|
571
|
+
} else if (process.platform === "darwin") {
|
|
572
|
+
// macOS
|
|
573
|
+
exec(`open ${url}`);
|
|
574
|
+
} else {
|
|
575
|
+
// Linux and others
|
|
576
|
+
exec(`xdg-open ${url}`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
async function tryGetProject(projectRoot) {
|
|
580
|
+
const valConfigFile = (await evalValConfigFile(projectRoot, "val.config.ts")) || (await evalValConfigFile(projectRoot, "val.config.js"));
|
|
581
|
+
if (valConfigFile && valConfigFile.project) {
|
|
582
|
+
const parts = valConfigFile.project.split("/");
|
|
583
|
+
if (parts.length === 2) {
|
|
584
|
+
return {
|
|
585
|
+
orgName: parts[0],
|
|
586
|
+
projectName: parts[1]
|
|
587
|
+
};
|
|
588
|
+
} else {
|
|
589
|
+
console.error(picocolors.red(`Invalid project format in val.config file: "${valConfigFile.project}". Expected format "orgName/projectName".`));
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
function getGitHubOwnerAndRepo(gitRemote) {
|
|
596
|
+
const sshMatch = gitRemote.match(/git@github\.com:(.+?)\/(.+?)(\.git)?$/);
|
|
597
|
+
if (sshMatch) {
|
|
598
|
+
return {
|
|
599
|
+
owner: sshMatch[1],
|
|
600
|
+
repo: sshMatch[2]
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
const httpsMatch = gitRemote.match(/https:\/\/github\.com\/(.+?)\/(.+?)(\.git)?$/);
|
|
604
|
+
if (httpsMatch) {
|
|
605
|
+
return {
|
|
606
|
+
owner: httpsMatch[1],
|
|
607
|
+
repo: httpsMatch[2]
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
async function tryGetGitRemote(root) {
|
|
613
|
+
try {
|
|
614
|
+
const gitConfig = tryGetGitConfig(root);
|
|
615
|
+
if (!gitConfig) {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
const remoteMatch = gitConfig.match(/\[remote "origin"\][\s\S]*?url = (.+)/);
|
|
619
|
+
if (remoteMatch && remoteMatch[1]) {
|
|
620
|
+
return remoteMatch[1].trim();
|
|
621
|
+
}
|
|
622
|
+
return null;
|
|
623
|
+
} catch (error) {
|
|
624
|
+
console.error(picocolors.red("Failed to read .git/config file."), error);
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function tryGetGitConfig(root) {
|
|
629
|
+
let currentDir = root;
|
|
630
|
+
let lastDir = null;
|
|
631
|
+
while (currentDir !== lastDir) {
|
|
632
|
+
const gitConfigPath = path.join(currentDir, ".git", "config");
|
|
633
|
+
if (fs$1.existsSync(gitConfigPath)) {
|
|
634
|
+
return fs$1.readFileSync(gitConfigPath, "utf-8");
|
|
635
|
+
}
|
|
636
|
+
lastDir = currentDir;
|
|
637
|
+
currentDir = path.dirname(currentDir);
|
|
638
|
+
if (lastDir === currentDir) {
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const host = process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
553
646
|
async function login(options) {
|
|
554
647
|
try {
|
|
555
648
|
var _response$headers$get;
|
|
@@ -566,7 +659,7 @@ async function login(options) {
|
|
|
566
659
|
let url;
|
|
567
660
|
if (!((_response$headers$get = response.headers.get("content-type")) !== null && _response$headers$get !== void 0 && _response$headers$get.includes("application/json"))) {
|
|
568
661
|
const text = await response.text();
|
|
569
|
-
console.error(picocolors.red("Unexpected failure while trying to login (content type was not JSON). Server response:
|
|
662
|
+
console.error(picocolors.red("Unexpected failure while trying to login (content type was not JSON). "), text ? `Server response: ${text} (status: ${response.status})` : `Status: ${response.status}`);
|
|
570
663
|
process.exit(1);
|
|
571
664
|
}
|
|
572
665
|
const json = await response.json();
|
|
@@ -598,7 +691,9 @@ async function pollForConfirmation(token) {
|
|
|
598
691
|
const start = Date.now();
|
|
599
692
|
while (Date.now() - start < MAX_DURATION) {
|
|
600
693
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
601
|
-
const response = await fetch(`${host}/api/login?token=${token}&consume=true
|
|
694
|
+
const response = await fetch(`${host}/api/login?token=${token}&consume=true`, {
|
|
695
|
+
method: "POST"
|
|
696
|
+
});
|
|
602
697
|
if (response.status === 500) {
|
|
603
698
|
console.error(picocolors.red("An error occurred on the server."));
|
|
604
699
|
process.exit(1);
|
|
@@ -606,7 +701,7 @@ async function pollForConfirmation(token) {
|
|
|
606
701
|
if (response.status === 200) {
|
|
607
702
|
const json = await response.json();
|
|
608
703
|
if (json) {
|
|
609
|
-
if (typeof json.profile.
|
|
704
|
+
if (typeof json.profile.email === "string" && typeof json.pat === "string") {
|
|
610
705
|
return json;
|
|
611
706
|
} else {
|
|
612
707
|
console.error(picocolors.red("Unexpected response from the server."));
|
|
@@ -623,7 +718,7 @@ function saveToken(result, filePath) {
|
|
|
623
718
|
recursive: true
|
|
624
719
|
});
|
|
625
720
|
fs$1.writeFileSync(filePath, JSON.stringify(result, null, 2));
|
|
626
|
-
console.log(picocolors.green(`Token for ${picocolors.cyan(result.profile.
|
|
721
|
+
console.log(picocolors.green(`Token for ${picocolors.cyan(result.profile.email)} saved to ${picocolors.cyan(filePath)}`));
|
|
627
722
|
}
|
|
628
723
|
|
|
629
724
|
async function main() {
|
|
@@ -641,7 +736,8 @@ async function main() {
|
|
|
641
736
|
Commands:
|
|
642
737
|
validate
|
|
643
738
|
login
|
|
644
|
-
|
|
739
|
+
files
|
|
740
|
+
connect
|
|
645
741
|
versions
|
|
646
742
|
|
|
647
743
|
Command: validate
|
|
@@ -652,21 +748,21 @@ async function main() {
|
|
|
652
748
|
|
|
653
749
|
|
|
654
750
|
Command: login
|
|
655
|
-
Description: login to
|
|
751
|
+
Description: login to admin.val.build and generate a Personal Access Token
|
|
656
752
|
Options:
|
|
657
753
|
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
658
754
|
|
|
659
755
|
|
|
660
|
-
Command:
|
|
661
|
-
Description:
|
|
662
|
-
|
|
663
|
-
|
|
756
|
+
Command: connect
|
|
757
|
+
Description: connect your local project to a Val Build project at admin.val.build
|
|
758
|
+
Options:
|
|
759
|
+
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
664
760
|
|
|
665
|
-
|
|
666
|
-
|
|
761
|
+
Command: list-unused-files
|
|
762
|
+
Description: EXPERIMENTAL.
|
|
763
|
+
List files that are in public/val but not in use by any Val module.
|
|
667
764
|
This is useful for cleaning up unused files.
|
|
668
765
|
Options:
|
|
669
|
-
--managedDir [dir] If set, list files found in directory that are not managed by Val
|
|
670
766
|
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
671
767
|
`, {
|
|
672
768
|
flags: {
|
|
@@ -699,13 +795,12 @@ async function main() {
|
|
|
699
795
|
}
|
|
700
796
|
const [command] = input;
|
|
701
797
|
switch (command) {
|
|
702
|
-
case "files":
|
|
798
|
+
case "list-unused-files":
|
|
703
799
|
if (flags.fix || flags.noEslint) {
|
|
704
|
-
return error(`Command "files" does not support --fix or --noEslint flags`);
|
|
800
|
+
return error(`Command "list-unused-files" does not support --fix or --noEslint flags`);
|
|
705
801
|
}
|
|
706
|
-
return
|
|
707
|
-
root: flags.root
|
|
708
|
-
managedDir: flags.managedDir
|
|
802
|
+
return listUnusedFiles({
|
|
803
|
+
root: flags.root
|
|
709
804
|
});
|
|
710
805
|
case "versions":
|
|
711
806
|
return versions();
|
|
@@ -713,6 +808,13 @@ async function main() {
|
|
|
713
808
|
return login({
|
|
714
809
|
root: flags.root
|
|
715
810
|
});
|
|
811
|
+
case "connect":
|
|
812
|
+
if (flags.fix || flags.noEslint) {
|
|
813
|
+
return error(`Command "connect" does not support --fix or --noEslint flags`);
|
|
814
|
+
}
|
|
815
|
+
return connect({
|
|
816
|
+
root: flags.root
|
|
817
|
+
});
|
|
716
818
|
case "validate":
|
|
717
819
|
case "idate":
|
|
718
820
|
if (flags.managedDir) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valbuild/cli",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.86.0",
|
|
5
5
|
"description": "Val CLI tools",
|
|
6
6
|
"bin": {
|
|
7
7
|
"val": "./bin.js"
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@inquirer/confirm": "^2.0.15",
|
|
22
22
|
"@inquirer/prompts": "^3.0.2",
|
|
23
|
-
"@valbuild/core": "~0.
|
|
24
|
-
"@valbuild/eslint-plugin": "~0.
|
|
25
|
-
"@valbuild/server": "~0.
|
|
23
|
+
"@valbuild/core": "~0.86.0",
|
|
24
|
+
"@valbuild/eslint-plugin": "~0.86.0",
|
|
25
|
+
"@valbuild/server": "~0.86.0",
|
|
26
26
|
"chalk": "^4.1.2",
|
|
27
27
|
"cors": "^2.8.5",
|
|
28
28
|
"express": "^4.18.2",
|
package/src/cli.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import meow from "meow";
|
|
2
2
|
import { error } from "./logger";
|
|
3
3
|
import { validate } from "./validate";
|
|
4
|
-
import {
|
|
4
|
+
import { listUnusedFiles as listUnusedFiles } from "./listUnusedFiles";
|
|
5
5
|
import { getVersions } from "./getVersions";
|
|
6
|
+
import { connect } from "./connect";
|
|
6
7
|
import chalk from "chalk";
|
|
7
8
|
import { login } from "./login";
|
|
8
9
|
|
|
@@ -18,7 +19,8 @@ async function main(): Promise<void> {
|
|
|
18
19
|
Commands:
|
|
19
20
|
validate
|
|
20
21
|
login
|
|
21
|
-
|
|
22
|
+
files
|
|
23
|
+
connect
|
|
22
24
|
versions
|
|
23
25
|
|
|
24
26
|
Command: validate
|
|
@@ -29,21 +31,21 @@ async function main(): Promise<void> {
|
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
Command: login
|
|
32
|
-
Description: login to
|
|
34
|
+
Description: login to admin.val.build and generate a Personal Access Token
|
|
33
35
|
Options:
|
|
34
36
|
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
35
37
|
|
|
36
38
|
|
|
37
|
-
Command:
|
|
38
|
-
Description:
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
Command: connect
|
|
40
|
+
Description: connect your local project to a Val Build project at admin.val.build
|
|
41
|
+
Options:
|
|
42
|
+
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
Command: list-unused-files
|
|
45
|
+
Description: EXPERIMENTAL.
|
|
46
|
+
List files that are in public/val but not in use by any Val module.
|
|
44
47
|
This is useful for cleaning up unused files.
|
|
45
48
|
Options:
|
|
46
|
-
--managedDir [dir] If set, list files found in directory that are not managed by Val
|
|
47
49
|
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
48
50
|
`,
|
|
49
51
|
{
|
|
@@ -81,15 +83,14 @@ async function main(): Promise<void> {
|
|
|
81
83
|
|
|
82
84
|
const [command] = input;
|
|
83
85
|
switch (command) {
|
|
84
|
-
case "files":
|
|
86
|
+
case "list-unused-files":
|
|
85
87
|
if (flags.fix || flags.noEslint) {
|
|
86
88
|
return error(
|
|
87
|
-
`Command "files" does not support --fix or --noEslint flags`,
|
|
89
|
+
`Command "list-unused-files" does not support --fix or --noEslint flags`,
|
|
88
90
|
);
|
|
89
91
|
}
|
|
90
|
-
return
|
|
92
|
+
return listUnusedFiles({
|
|
91
93
|
root: flags.root,
|
|
92
|
-
managedDir: flags.managedDir,
|
|
93
94
|
});
|
|
94
95
|
case "versions":
|
|
95
96
|
return versions();
|
|
@@ -97,6 +98,15 @@ async function main(): Promise<void> {
|
|
|
97
98
|
return login({
|
|
98
99
|
root: flags.root,
|
|
99
100
|
});
|
|
101
|
+
case "connect":
|
|
102
|
+
if (flags.fix || flags.noEslint) {
|
|
103
|
+
return error(
|
|
104
|
+
`Command "connect" does not support --fix or --noEslint flags`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return connect({
|
|
108
|
+
root: flags.root,
|
|
109
|
+
});
|
|
100
110
|
case "validate":
|
|
101
111
|
case "idate":
|
|
102
112
|
if (flags.managedDir) {
|
package/src/connect.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { evalValConfigFile } from "./utils/evalValConfigFile";
|
|
5
|
+
import { exec } from "child_process";
|
|
6
|
+
|
|
7
|
+
const host = process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
8
|
+
|
|
9
|
+
export async function connect(options: { root?: string }) {
|
|
10
|
+
const { root } = options;
|
|
11
|
+
const projectRoot = root ? path.resolve(root) : process.cwd();
|
|
12
|
+
|
|
13
|
+
const maybeProject = await tryGetProject(projectRoot);
|
|
14
|
+
const maybeGitRemote = await tryGetGitRemote(projectRoot);
|
|
15
|
+
const maybeGitOwnerAndRepo =
|
|
16
|
+
maybeGitRemote !== null ? getGitHubOwnerAndRepo(maybeGitRemote) : null;
|
|
17
|
+
|
|
18
|
+
const params = new URLSearchParams();
|
|
19
|
+
if (maybeProject) {
|
|
20
|
+
params.set("org", maybeProject.orgName);
|
|
21
|
+
params.set("project", maybeProject.projectName);
|
|
22
|
+
}
|
|
23
|
+
if (maybeGitOwnerAndRepo) {
|
|
24
|
+
params.set(
|
|
25
|
+
"github_repo",
|
|
26
|
+
[maybeGitOwnerAndRepo.owner, maybeGitOwnerAndRepo.repo].join("/"),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
const url = `${host}/connect?${params.toString()}`;
|
|
30
|
+
|
|
31
|
+
// Open url in default browser and show fallback instructions:
|
|
32
|
+
console.log(pc.cyan("\nStarting connect process in browser...\n"));
|
|
33
|
+
console.log(
|
|
34
|
+
pc.dim(`\nIf the browser does not open, please visit:\n${url}\n`),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (process.platform === "win32") {
|
|
38
|
+
// Windows
|
|
39
|
+
exec(`start ${url}`);
|
|
40
|
+
} else if (process.platform === "darwin") {
|
|
41
|
+
// macOS
|
|
42
|
+
exec(`open ${url}`);
|
|
43
|
+
} else {
|
|
44
|
+
// Linux and others
|
|
45
|
+
exec(`xdg-open ${url}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function tryGetProject(projectRoot: string): Promise<{
|
|
50
|
+
orgName: string;
|
|
51
|
+
projectName: string;
|
|
52
|
+
} | null> {
|
|
53
|
+
const valConfigFile =
|
|
54
|
+
(await evalValConfigFile(projectRoot, "val.config.ts")) ||
|
|
55
|
+
(await evalValConfigFile(projectRoot, "val.config.js"));
|
|
56
|
+
|
|
57
|
+
if (valConfigFile && valConfigFile.project) {
|
|
58
|
+
const parts = valConfigFile.project.split("/");
|
|
59
|
+
if (parts.length === 2) {
|
|
60
|
+
return {
|
|
61
|
+
orgName: parts[0],
|
|
62
|
+
projectName: parts[1],
|
|
63
|
+
};
|
|
64
|
+
} else {
|
|
65
|
+
console.error(
|
|
66
|
+
pc.red(
|
|
67
|
+
`Invalid project format in val.config file: "${valConfigFile.project}". Expected format "orgName/projectName".`,
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getGitHubOwnerAndRepo(
|
|
77
|
+
gitRemote: string,
|
|
78
|
+
): { owner: string; repo: string } | null {
|
|
79
|
+
const sshMatch = gitRemote.match(/git@github\.com:(.+?)\/(.+?)(\.git)?$/);
|
|
80
|
+
if (sshMatch) {
|
|
81
|
+
return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
82
|
+
}
|
|
83
|
+
const httpsMatch = gitRemote.match(
|
|
84
|
+
/https:\/\/github\.com\/(.+?)\/(.+?)(\.git)?$/,
|
|
85
|
+
);
|
|
86
|
+
if (httpsMatch) {
|
|
87
|
+
return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function tryGetGitRemote(root: string): Promise<string | null> {
|
|
93
|
+
try {
|
|
94
|
+
const gitConfig = tryGetGitConfig(root);
|
|
95
|
+
if (!gitConfig) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const remoteMatch = gitConfig.match(
|
|
99
|
+
/\[remote "origin"\][\s\S]*?url = (.+)/,
|
|
100
|
+
);
|
|
101
|
+
if (remoteMatch && remoteMatch[1]) {
|
|
102
|
+
return remoteMatch[1].trim();
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(pc.red("Failed to read .git/config file."), error);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function tryGetGitConfig(root: string): string | null {
|
|
112
|
+
let currentDir = root;
|
|
113
|
+
let lastDir = null;
|
|
114
|
+
while (currentDir !== lastDir) {
|
|
115
|
+
const gitConfigPath = path.join(currentDir, ".git", "config");
|
|
116
|
+
if (fs.existsSync(gitConfigPath)) {
|
|
117
|
+
return fs.readFileSync(gitConfigPath, "utf-8");
|
|
118
|
+
}
|
|
119
|
+
lastDir = currentDir;
|
|
120
|
+
currentDir = path.dirname(currentDir);
|
|
121
|
+
if (lastDir === currentDir) {
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
@@ -9,14 +9,8 @@ import { createService } from "@valbuild/server";
|
|
|
9
9
|
import { glob } from "fast-glob";
|
|
10
10
|
import path from "path";
|
|
11
11
|
|
|
12
|
-
export async function
|
|
13
|
-
|
|
14
|
-
managedDir,
|
|
15
|
-
}: {
|
|
16
|
-
root?: string;
|
|
17
|
-
managedDir?: string;
|
|
18
|
-
}) {
|
|
19
|
-
const printFilesUsedByVal = !managedDir;
|
|
12
|
+
export async function listUnusedFiles({ root }: { root?: string }) {
|
|
13
|
+
const managedDir = "public/val";
|
|
20
14
|
const projectRoot = root ? path.resolve(root) : process.cwd();
|
|
21
15
|
|
|
22
16
|
const service = await createService(projectRoot, {});
|
|
@@ -26,8 +20,8 @@ export async function files({
|
|
|
26
20
|
cwd: projectRoot,
|
|
27
21
|
});
|
|
28
22
|
|
|
29
|
-
const
|
|
30
|
-
async function
|
|
23
|
+
const filesUsedByVal: string[] = [];
|
|
24
|
+
async function pushFilesUsedByVal(file: string) {
|
|
31
25
|
const moduleId = `/${file}` as ModuleFilePath; // TODO: check if this always works? (Windows?)
|
|
32
26
|
const valModule = await service.get(moduleId, "" as ModulePath, {
|
|
33
27
|
validate: true,
|
|
@@ -47,11 +41,7 @@ export async function files({
|
|
|
47
41
|
projectRoot,
|
|
48
42
|
...value[FILE_REF_PROP].split("/"),
|
|
49
43
|
);
|
|
50
|
-
|
|
51
|
-
console.log(absoluteFilePathUsedByVal);
|
|
52
|
-
} else {
|
|
53
|
-
absoluteFilesPathUsedByVal.push(absoluteFilePathUsedByVal);
|
|
54
|
-
}
|
|
44
|
+
filesUsedByVal.push(absoluteFilePathUsedByVal);
|
|
55
45
|
}
|
|
56
46
|
}
|
|
57
47
|
}
|
|
@@ -59,22 +49,18 @@ export async function files({
|
|
|
59
49
|
}
|
|
60
50
|
}
|
|
61
51
|
for (const file of valFiles) {
|
|
62
|
-
await
|
|
52
|
+
await pushFilesUsedByVal(file);
|
|
63
53
|
}
|
|
64
54
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const absoluteFilePath = path.join(managedRoot, file);
|
|
75
|
-
if (!absoluteFilesPathUsedByVal.includes(absoluteFilePath)) {
|
|
76
|
-
console.log(path.join(managedRoot, file));
|
|
77
|
-
}
|
|
55
|
+
const managedRoot = path.join(projectRoot, managedDir);
|
|
56
|
+
const allFilesInManagedDir = await glob("**/*", {
|
|
57
|
+
ignore: ["node_modules/**"],
|
|
58
|
+
cwd: managedRoot,
|
|
59
|
+
});
|
|
60
|
+
for (const file of allFilesInManagedDir) {
|
|
61
|
+
const absoluteFilePath = path.join(managedRoot, file);
|
|
62
|
+
if (!filesUsedByVal.includes(absoluteFilePath)) {
|
|
63
|
+
console.log(path.join(managedRoot, file));
|
|
78
64
|
}
|
|
79
65
|
}
|
|
80
66
|
|
package/src/login.ts
CHANGED
|
@@ -3,7 +3,7 @@ import fs from "fs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { getPersonalAccessTokenPath } from "@valbuild/server";
|
|
5
5
|
|
|
6
|
-
const host = process.env.VAL_BUILD_URL || "https://
|
|
6
|
+
const host = process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
7
7
|
|
|
8
8
|
export async function login(options: { root?: string }) {
|
|
9
9
|
try {
|
|
@@ -22,9 +22,11 @@ export async function login(options: { root?: string }) {
|
|
|
22
22
|
const text = await response.text();
|
|
23
23
|
console.error(
|
|
24
24
|
pc.red(
|
|
25
|
-
"Unexpected failure while trying to login (content type was not JSON).
|
|
25
|
+
"Unexpected failure while trying to login (content type was not JSON). ",
|
|
26
26
|
),
|
|
27
|
-
text
|
|
27
|
+
text
|
|
28
|
+
? `Server response: ${text} (status: ${response.status})`
|
|
29
|
+
: `Status: ${response.status}`,
|
|
28
30
|
);
|
|
29
31
|
process.exit(1);
|
|
30
32
|
}
|
|
@@ -61,7 +63,7 @@ export async function login(options: { root?: string }) {
|
|
|
61
63
|
|
|
62
64
|
const MAX_DURATION = 5 * 60 * 1000; // 5 minutes
|
|
63
65
|
async function pollForConfirmation(token: string): Promise<{
|
|
64
|
-
profile: {
|
|
66
|
+
profile: { email: string };
|
|
65
67
|
pat: string;
|
|
66
68
|
}> {
|
|
67
69
|
const start = Date.now();
|
|
@@ -69,6 +71,7 @@ async function pollForConfirmation(token: string): Promise<{
|
|
|
69
71
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
70
72
|
const response = await fetch(
|
|
71
73
|
`${host}/api/login?token=${token}&consume=true`,
|
|
74
|
+
{ method: "POST" },
|
|
72
75
|
);
|
|
73
76
|
if (response.status === 500) {
|
|
74
77
|
console.error(pc.red("An error occurred on the server."));
|
|
@@ -78,7 +81,7 @@ async function pollForConfirmation(token: string): Promise<{
|
|
|
78
81
|
const json = await response.json();
|
|
79
82
|
if (json) {
|
|
80
83
|
if (
|
|
81
|
-
typeof json.profile.
|
|
84
|
+
typeof json.profile.email === "string" &&
|
|
82
85
|
typeof json.pat === "string"
|
|
83
86
|
) {
|
|
84
87
|
return json;
|
|
@@ -95,7 +98,7 @@ async function pollForConfirmation(token: string): Promise<{
|
|
|
95
98
|
|
|
96
99
|
function saveToken(
|
|
97
100
|
result: {
|
|
98
|
-
profile: {
|
|
101
|
+
profile: { email: string };
|
|
99
102
|
pat: string;
|
|
100
103
|
},
|
|
101
104
|
filePath: string,
|
|
@@ -105,7 +108,7 @@ function saveToken(
|
|
|
105
108
|
console.log(
|
|
106
109
|
pc.green(
|
|
107
110
|
`Token for ${pc.cyan(
|
|
108
|
-
result.profile.
|
|
111
|
+
result.profile.email,
|
|
109
112
|
)} saved to ${pc.cyan(filePath)}`,
|
|
110
113
|
),
|
|
111
114
|
);
|