clispark 1.18.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +425 -92
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/powershell/ARCHITECTURE.md +26 -0
- package/templates/powershell/Logging/Initialize-Logging.ps1 +27 -0
- package/templates/powershell/Module.psd1 +15 -0
- package/templates/powershell/Module.psm1 +70 -0
- package/templates/powershell/Private/.gitkeep +0 -0
- package/templates/powershell/Public/Get-Hello.ps1 +10 -0
- package/templates/powershell/README.md +26 -0
- package/templates/powershell/gitignore +2 -0
- package/templates/powershell/tests/Get-Hello.Tests.ps1 +9 -0
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
4
|
+
import path19 from "path";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/wizard.ts
|
|
8
|
-
import { intro, outro, text, select, log, isCancel, cancel } from "@clack/prompts";
|
|
8
|
+
import { intro, outro, text as text2, select as select2, log, isCancel as isCancel2, cancel as cancel2 } from "@clack/prompts";
|
|
9
9
|
|
|
10
10
|
// src/languages/packs/node-oclif.ts
|
|
11
11
|
import path5 from "path";
|
|
@@ -694,57 +694,385 @@ var dotnetPack = {
|
|
|
694
694
|
commandGenerator: dotnetCommandGenerator
|
|
695
695
|
};
|
|
696
696
|
|
|
697
|
+
// src/languages/packs/powershell.ts
|
|
698
|
+
import path13 from "path";
|
|
699
|
+
|
|
700
|
+
// src/update/adapters/powershell.ts
|
|
701
|
+
import { readFile as readFile6, writeFile as writeFile7 } from "fs/promises";
|
|
702
|
+
import path10 from "path";
|
|
703
|
+
import spawn from "cross-spawn";
|
|
704
|
+
var CORE_FILE_PATHS3 = [
|
|
705
|
+
"Module.psm1",
|
|
706
|
+
"Logging/Initialize-Logging.ps1",
|
|
707
|
+
"ARCHITECTURE.md",
|
|
708
|
+
".gitignore"
|
|
709
|
+
];
|
|
710
|
+
function arrayEquals(a, b) {
|
|
711
|
+
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
712
|
+
}
|
|
713
|
+
function escapeSingleQuotedPowerShellString(value) {
|
|
714
|
+
return value.replace(/'/g, "''");
|
|
715
|
+
}
|
|
716
|
+
function readManifestViaPwsh(manifestPath) {
|
|
717
|
+
return new Promise((resolve, reject) => {
|
|
718
|
+
const child = spawn(
|
|
719
|
+
"pwsh",
|
|
720
|
+
[
|
|
721
|
+
"-NoProfile",
|
|
722
|
+
"-Command",
|
|
723
|
+
`(Import-PowerShellDataFile -Path '${escapeSingleQuotedPowerShellString(manifestPath)}') | ConvertTo-Json -Depth 5 -Compress`
|
|
724
|
+
],
|
|
725
|
+
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
726
|
+
);
|
|
727
|
+
let stdout = "";
|
|
728
|
+
let stderr = "";
|
|
729
|
+
child.stdout?.on("data", (chunk) => stdout += chunk);
|
|
730
|
+
child.stderr?.on("data", (chunk) => stderr += chunk);
|
|
731
|
+
child.on("error", reject);
|
|
732
|
+
child.on("close", (code) => {
|
|
733
|
+
if (code !== 0) {
|
|
734
|
+
reject(new Error(`pwsh exited with code ${code} reading ${manifestPath}: ${stderr}`));
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
try {
|
|
738
|
+
resolve(JSON.parse(stdout));
|
|
739
|
+
} catch (err) {
|
|
740
|
+
reject(new Error(`Could not parse pwsh JSON output for ${manifestPath}: ${String(err)}
|
|
741
|
+
Output: ${stdout}`));
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
function parseManifestFile2(rawContent) {
|
|
747
|
+
const versionMatch = rawContent.match(/ModuleVersion\s*=\s*'([^']*)'/);
|
|
748
|
+
if (!versionMatch) throw new Error("Module.psd1 is missing a ModuleVersion field");
|
|
749
|
+
const requiredModulesMatch = rawContent.match(/RequiredModules\s*=\s*@\(([^)]*)\)/);
|
|
750
|
+
const requiredModules = requiredModulesMatch ? requiredModulesMatch[1].split(",").map((s) => s.trim().replace(/^'|'$/g, "")).filter(Boolean) : [];
|
|
751
|
+
return { raw: rawContent, version: versionMatch[1], requiredModules };
|
|
752
|
+
}
|
|
753
|
+
function setModuleVersion(content, version) {
|
|
754
|
+
return content.replace(/(ModuleVersion\s*=\s*')[^']*(')/, `$1${version}$2`);
|
|
755
|
+
}
|
|
756
|
+
function setRequiredModules(content, modules) {
|
|
757
|
+
const formatted = modules.map((m) => `'${m}'`).join(", ");
|
|
758
|
+
return content.replace(/(RequiredModules\s*=\s*@\()[^)]*(\))/, `$1${formatted}$2`);
|
|
759
|
+
}
|
|
760
|
+
function extractCoreFields3(manifestFile) {
|
|
761
|
+
const coreDependencies = {};
|
|
762
|
+
for (const name of manifestFile.requiredModules) coreDependencies[name] = "*";
|
|
763
|
+
return {
|
|
764
|
+
coreDependencies,
|
|
765
|
+
coreScripts: {},
|
|
766
|
+
coreFields: { RequiredModulesCount: manifestFile.requiredModules.length, ModuleVersion: manifestFile.version }
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function mergeManifestFile2(current, oldManifest, newTemplate) {
|
|
770
|
+
let raw = current.raw;
|
|
771
|
+
let changed = false;
|
|
772
|
+
const dependencies = [];
|
|
773
|
+
const coreDependencies = {};
|
|
774
|
+
const mergedModules = [];
|
|
775
|
+
for (const name of newTemplate.requiredModules) {
|
|
776
|
+
const currentHasIt = current.requiredModules.includes(name);
|
|
777
|
+
const oldHadIt = Object.prototype.hasOwnProperty.call(oldManifest.coreDependencies, name);
|
|
778
|
+
const result = reconcileEntry(
|
|
779
|
+
currentHasIt ? name : void 0,
|
|
780
|
+
oldHadIt ? name : void 0,
|
|
781
|
+
name,
|
|
782
|
+
(a, b) => a === b
|
|
783
|
+
);
|
|
784
|
+
dependencies.push({ key: name, outcome: result.outcome });
|
|
785
|
+
if (result.value) {
|
|
786
|
+
coreDependencies[name] = "*";
|
|
787
|
+
mergedModules.push(result.value);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
for (const name of current.requiredModules) {
|
|
791
|
+
if (!mergedModules.includes(name)) mergedModules.push(name);
|
|
792
|
+
}
|
|
793
|
+
if (!arrayEquals(mergedModules, current.requiredModules)) {
|
|
794
|
+
changed = true;
|
|
795
|
+
raw = setRequiredModules(raw, mergedModules);
|
|
796
|
+
}
|
|
797
|
+
const oldCoreFields = oldManifest.coreFields;
|
|
798
|
+
const versionResult = reconcileEntry(current.version, oldCoreFields.ModuleVersion, newTemplate.version, (a, b) => a === b);
|
|
799
|
+
if (versionResult.value !== current.version) {
|
|
800
|
+
changed = true;
|
|
801
|
+
raw = setModuleVersion(raw, versionResult.value);
|
|
802
|
+
}
|
|
803
|
+
return {
|
|
804
|
+
updatedFile: { ...current, raw, requiredModules: mergedModules },
|
|
805
|
+
changed,
|
|
806
|
+
dependencies,
|
|
807
|
+
scripts: [],
|
|
808
|
+
fields: [],
|
|
809
|
+
coreDependencies,
|
|
810
|
+
coreScripts: {},
|
|
811
|
+
coreFields: { RequiredModulesCount: mergedModules.length, ModuleVersion: versionResult.value }
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
var powershellAdapter = {
|
|
815
|
+
coreFilePaths: CORE_FILE_PATHS3,
|
|
816
|
+
templateSourcePath(relativePath) {
|
|
817
|
+
return relativePath === ".gitignore" ? "gitignore" : relativePath;
|
|
818
|
+
},
|
|
819
|
+
manifestFileName: "Module.psd1",
|
|
820
|
+
async readManifestFile(dir) {
|
|
821
|
+
const manifestPath = path10.join(dir, "Module.psd1");
|
|
822
|
+
const raw = await readFile6(manifestPath, "utf8");
|
|
823
|
+
const parsedViaPwsh = await readManifestViaPwsh(manifestPath);
|
|
824
|
+
return {
|
|
825
|
+
raw,
|
|
826
|
+
version: parsedViaPwsh.ModuleVersion,
|
|
827
|
+
requiredModules: parsedViaPwsh.RequiredModules ?? []
|
|
828
|
+
};
|
|
829
|
+
},
|
|
830
|
+
async writeManifestFile(dir, content) {
|
|
831
|
+
await writeFile7(path10.join(dir, "Module.psd1"), content.raw);
|
|
832
|
+
},
|
|
833
|
+
parseManifestFile: parseManifestFile2,
|
|
834
|
+
readProjectName() {
|
|
835
|
+
return "__scaffolded-from-targetDir__";
|
|
836
|
+
},
|
|
837
|
+
extractCoreFields(manifestFile) {
|
|
838
|
+
return extractCoreFields3(manifestFile);
|
|
839
|
+
},
|
|
840
|
+
mergeManifestFile(current, oldManifest, newTemplate) {
|
|
841
|
+
return mergeManifestFile2(current, oldManifest, newTemplate);
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
// src/languages/registry-checkers/powershell-gallery.ts
|
|
846
|
+
import { mkdir as mkdir4, writeFile as writeFile8 } from "fs/promises";
|
|
847
|
+
import path11 from "path";
|
|
848
|
+
var POWERSHELL_GALLERY_DEFAULT_URL = "https://www.powershellgallery.com/api/v2";
|
|
849
|
+
var FETCH_TIMEOUT_MS3 = 5e3;
|
|
850
|
+
async function checkNameAvailability3(name, registryUrl) {
|
|
851
|
+
const url = `${registryUrl}/FindPackagesById()?id='${encodeURIComponent(name)}'`;
|
|
852
|
+
try {
|
|
853
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS3) });
|
|
854
|
+
if (response.status !== 200) return "unverified";
|
|
855
|
+
const body = await response.text();
|
|
856
|
+
return body.includes("<entry>") ? "taken" : "available";
|
|
857
|
+
} catch {
|
|
858
|
+
return "unverified";
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
async function applyPrivateIntent3() {
|
|
862
|
+
}
|
|
863
|
+
async function applyRegistryUrl3(targetDir, registryUrl) {
|
|
864
|
+
const content = [
|
|
865
|
+
"# This file documents the private PSResourceGet repository configured for this project.",
|
|
866
|
+
"# Register it once per machine before publishing or installing dependencies from it:",
|
|
867
|
+
`# Register-PSResourceRepository -Name "custom" -Uri "${registryUrl}" -Trusted`,
|
|
868
|
+
"",
|
|
869
|
+
registryUrl,
|
|
870
|
+
""
|
|
871
|
+
].join("\n");
|
|
872
|
+
await mkdir4(targetDir, { recursive: true });
|
|
873
|
+
await writeFile8(path11.join(targetDir, ".psresource-repository"), content);
|
|
874
|
+
}
|
|
875
|
+
var powershellGalleryRegistryChecker = {
|
|
876
|
+
checkNameAvailability: checkNameAvailability3,
|
|
877
|
+
applyPrivateIntent: applyPrivateIntent3,
|
|
878
|
+
applyRegistryUrl: applyRegistryUrl3
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
// src/languages/command-generators/powershell.ts
|
|
882
|
+
import { mkdir as mkdir5, readdir as readdir3, writeFile as writeFile9 } from "fs/promises";
|
|
883
|
+
import path12 from "path";
|
|
884
|
+
import { select, text, isCancel, cancel } from "@clack/prompts";
|
|
885
|
+
var APPROVED_VERBS = [
|
|
886
|
+
"Get",
|
|
887
|
+
"Set",
|
|
888
|
+
"New",
|
|
889
|
+
"Remove",
|
|
890
|
+
"Add",
|
|
891
|
+
"Clear",
|
|
892
|
+
"Invoke",
|
|
893
|
+
"Start",
|
|
894
|
+
"Stop",
|
|
895
|
+
"Test",
|
|
896
|
+
"Update",
|
|
897
|
+
"Export",
|
|
898
|
+
"Import"
|
|
899
|
+
];
|
|
900
|
+
function exitIfCancelled(value) {
|
|
901
|
+
if (isCancel(value)) {
|
|
902
|
+
cancel("Operation cancelled.");
|
|
903
|
+
process.exit(1);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
async function listExistingCommands3(targetDir) {
|
|
907
|
+
const publicDir = path12.join(targetDir, "Public");
|
|
908
|
+
const files = await readdir3(publicDir);
|
|
909
|
+
const paths2 = files.filter((f) => f.endsWith(".ps1")).map((f) => f.replace(/\.ps1$/, ""));
|
|
910
|
+
return buildCommandTree(paths2);
|
|
911
|
+
}
|
|
912
|
+
function powershellParamType(param) {
|
|
913
|
+
if (param.type === "integer") return "[int]";
|
|
914
|
+
if (param.type === "boolean") return "[switch]";
|
|
915
|
+
return "[string]";
|
|
916
|
+
}
|
|
917
|
+
function parameterDeclaration(param) {
|
|
918
|
+
const lines = [];
|
|
919
|
+
if (param.type === "enum") {
|
|
920
|
+
lines.push(` [ValidateSet(${(param.allowedValues ?? []).map((v) => `'${v}'`).join(", ")})]`);
|
|
921
|
+
}
|
|
922
|
+
if (param.required && param.type !== "boolean") {
|
|
923
|
+
lines.push(" [Parameter(Mandatory)]");
|
|
924
|
+
}
|
|
925
|
+
lines.push(` ${powershellParamType(param)}$${param.name}`);
|
|
926
|
+
return lines.join("\n");
|
|
927
|
+
}
|
|
928
|
+
function generateCommandFileContent3(spec) {
|
|
929
|
+
const funcName = spec.pathSegments[spec.pathSegments.length - 1];
|
|
930
|
+
const paramLines = spec.parameters.map(parameterDeclaration).join(",\n\n");
|
|
931
|
+
const paramBlock = spec.parameters.length > 0 ? `
|
|
932
|
+
${paramLines}
|
|
933
|
+
` : "";
|
|
934
|
+
const echoLines = spec.parameters.length > 0 ? spec.parameters.map((p) => ` Write-Output "${p.name}=$${p.name}"`).join("\n") : ` Write-Output '${funcName} ran'`;
|
|
935
|
+
return `function ${funcName} {
|
|
936
|
+
[CmdletBinding()]
|
|
937
|
+
param(${paramBlock})
|
|
938
|
+
process {
|
|
939
|
+
${echoLines}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
`;
|
|
943
|
+
}
|
|
944
|
+
function sampleParamValue(param) {
|
|
945
|
+
if (param.type === "enum") return param.allowedValues?.[0] ?? "";
|
|
946
|
+
if (param.type === "integer") return "1";
|
|
947
|
+
return "value";
|
|
948
|
+
}
|
|
949
|
+
function generateTestFileContent3(spec) {
|
|
950
|
+
const funcName = spec.pathSegments[spec.pathSegments.length - 1];
|
|
951
|
+
const mandatoryArgs = spec.parameters.filter((p) => p.required && p.type !== "boolean").map((p) => `-${p.name} '${sampleParamValue(p)}'`).join(" ");
|
|
952
|
+
const invocation = mandatoryArgs ? `${funcName} ${mandatoryArgs}` : funcName;
|
|
953
|
+
return `Describe '${funcName}' {
|
|
954
|
+
It 'runs without error' {
|
|
955
|
+
{ ${invocation} } | Should -Not -Throw
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
`;
|
|
959
|
+
}
|
|
960
|
+
async function generateCommand3(targetDir, spec) {
|
|
961
|
+
const funcName = spec.pathSegments[spec.pathSegments.length - 1];
|
|
962
|
+
const commandRelPath = path12.join("Public", `${funcName}.ps1`);
|
|
963
|
+
const testRelPath = path12.join("tests", `${funcName}.Tests.ps1`);
|
|
964
|
+
await mkdir5(path12.join(targetDir, "Public"), { recursive: true });
|
|
965
|
+
await mkdir5(path12.join(targetDir, "tests"), { recursive: true });
|
|
966
|
+
await writeFile9(path12.join(targetDir, commandRelPath), generateCommandFileContent3(spec));
|
|
967
|
+
await writeFile9(path12.join(targetDir, testRelPath), generateTestFileContent3(spec));
|
|
968
|
+
return { commandFile: commandRelPath.replace(/\\/g, "/"), testFile: testRelPath.replace(/\\/g, "/") };
|
|
969
|
+
}
|
|
970
|
+
async function promptCommandIdentity(_pathSegments, existingPaths) {
|
|
971
|
+
const verbValue = await select({
|
|
972
|
+
message: "Verb (from PowerShell\u2019s approved-verb list)",
|
|
973
|
+
options: APPROVED_VERBS.map((v) => ({ value: v, label: v }))
|
|
974
|
+
});
|
|
975
|
+
exitIfCancelled(verbValue);
|
|
976
|
+
const nounValue = await text({
|
|
977
|
+
message: "Noun",
|
|
978
|
+
validate: (value) => {
|
|
979
|
+
if (!/^[A-Z][A-Za-z0-9]*$/.test(value)) return "Use PascalCase, starting with an uppercase letter (e.g. TaskList).";
|
|
980
|
+
const fullName = `${verbValue}-${value}`;
|
|
981
|
+
if (existingPaths.has(fullName)) return `"${fullName}" already exists.`;
|
|
982
|
+
return void 0;
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
exitIfCancelled(nounValue);
|
|
986
|
+
return [`${verbValue}-${nounValue}`];
|
|
987
|
+
}
|
|
988
|
+
var powershellCommandGenerator = {
|
|
989
|
+
listExistingCommands: listExistingCommands3,
|
|
990
|
+
generateCommand: generateCommand3,
|
|
991
|
+
promptCommandIdentity
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
// src/languages/packs/powershell.ts
|
|
995
|
+
function validateProjectName3(value) {
|
|
996
|
+
if (!value || value.trim().length === 0) return "Project name is required.";
|
|
997
|
+
if (!/^[A-Z][A-Za-z0-9]*$/.test(value)) {
|
|
998
|
+
return "Use PascalCase, starting with an uppercase letter (e.g. MyTool).";
|
|
999
|
+
}
|
|
1000
|
+
return void 0;
|
|
1001
|
+
}
|
|
1002
|
+
var powershellPack = {
|
|
1003
|
+
id: "powershell",
|
|
1004
|
+
displayName: "PowerShell (7.4+)",
|
|
1005
|
+
templateDir: path13.join(findPackageRoot(), "templates", "powershell"),
|
|
1006
|
+
scaffoldCommands: [
|
|
1007
|
+
{
|
|
1008
|
+
command: "pwsh",
|
|
1009
|
+
args: ["-NoProfile", "-Command", "Install-Module -Name PSFramework,Pester,Microsoft.PowerShell.PSResourceGet -Scope CurrentUser -Force -AllowClobber"]
|
|
1010
|
+
}
|
|
1011
|
+
],
|
|
1012
|
+
validateProjectName: validateProjectName3,
|
|
1013
|
+
updateAdapter: powershellAdapter,
|
|
1014
|
+
registry: {
|
|
1015
|
+
defaultUrl: POWERSHELL_GALLERY_DEFAULT_URL,
|
|
1016
|
+
promptLabel: "Custom PowerShell repository URL (leave empty for the PowerShell Gallery)",
|
|
1017
|
+
checkNameAvailability: powershellGalleryRegistryChecker.checkNameAvailability,
|
|
1018
|
+
applyPrivateIntent: powershellGalleryRegistryChecker.applyPrivateIntent,
|
|
1019
|
+
applyRegistryUrl: powershellGalleryRegistryChecker.applyRegistryUrl
|
|
1020
|
+
},
|
|
1021
|
+
commandGenerator: powershellCommandGenerator
|
|
1022
|
+
};
|
|
1023
|
+
|
|
697
1024
|
// src/languages/index.ts
|
|
698
1025
|
var LANGUAGE_PACKS = {
|
|
699
1026
|
[nodeOclifPack.id]: nodeOclifPack,
|
|
700
|
-
[dotnetPack.id]: dotnetPack
|
|
1027
|
+
[dotnetPack.id]: dotnetPack,
|
|
1028
|
+
[powershellPack.id]: powershellPack
|
|
701
1029
|
};
|
|
702
1030
|
|
|
703
1031
|
// src/wizard.ts
|
|
704
1032
|
var defaultDeps = {
|
|
705
1033
|
languagePacks: LANGUAGE_PACKS
|
|
706
1034
|
};
|
|
707
|
-
function
|
|
708
|
-
if (
|
|
709
|
-
|
|
1035
|
+
function exitIfCancelled2(value) {
|
|
1036
|
+
if (isCancel2(value)) {
|
|
1037
|
+
cancel2("Operation cancelled.");
|
|
710
1038
|
process.exit(1);
|
|
711
1039
|
}
|
|
712
1040
|
}
|
|
713
1041
|
async function runWizard(deps = defaultDeps) {
|
|
714
1042
|
intro("clispark \u2014 scaffold a new CLI project");
|
|
715
1043
|
const packs = Object.values(deps.languagePacks);
|
|
716
|
-
const languageValue = await
|
|
1044
|
+
const languageValue = await select2({
|
|
717
1045
|
message: "Which language?",
|
|
718
1046
|
options: packs.map((pack2) => ({ value: pack2.id, label: pack2.displayName }))
|
|
719
1047
|
});
|
|
720
|
-
|
|
1048
|
+
exitIfCancelled2(languageValue);
|
|
721
1049
|
const pack = deps.languagePacks[languageValue];
|
|
722
|
-
const nameValue = await
|
|
1050
|
+
const nameValue = await text2({
|
|
723
1051
|
message: "Project name",
|
|
724
1052
|
validate: pack.validateProjectName
|
|
725
1053
|
});
|
|
726
|
-
|
|
1054
|
+
exitIfCancelled2(nameValue);
|
|
727
1055
|
let projectName = nameValue;
|
|
728
|
-
const profileValue = await
|
|
1056
|
+
const profileValue = await select2({
|
|
729
1057
|
message: "Is this a work or private project?",
|
|
730
1058
|
options: [
|
|
731
1059
|
{ value: "private", label: "Private" },
|
|
732
1060
|
{ value: "work", label: "Work" }
|
|
733
1061
|
]
|
|
734
1062
|
});
|
|
735
|
-
|
|
1063
|
+
exitIfCancelled2(profileValue);
|
|
736
1064
|
const profile = profileValue;
|
|
737
1065
|
let registryUrl = pack.registry.defaultUrl;
|
|
738
1066
|
if (profile === "work") {
|
|
739
|
-
const registryValue = await
|
|
1067
|
+
const registryValue = await text2({
|
|
740
1068
|
message: pack.registry.promptLabel,
|
|
741
1069
|
placeholder: pack.registry.defaultUrl,
|
|
742
1070
|
defaultValue: pack.registry.defaultUrl
|
|
743
1071
|
});
|
|
744
|
-
|
|
1072
|
+
exitIfCancelled2(registryValue);
|
|
745
1073
|
registryUrl = registryValue || pack.registry.defaultUrl;
|
|
746
1074
|
}
|
|
747
|
-
const publishIntentValue = await
|
|
1075
|
+
const publishIntentValue = await select2({
|
|
748
1076
|
message: "Do you plan to publish this?",
|
|
749
1077
|
options: [
|
|
750
1078
|
{ value: false, label: "No" },
|
|
@@ -752,18 +1080,18 @@ async function runWizard(deps = defaultDeps) {
|
|
|
752
1080
|
],
|
|
753
1081
|
initialValue: false
|
|
754
1082
|
});
|
|
755
|
-
|
|
1083
|
+
exitIfCancelled2(publishIntentValue);
|
|
756
1084
|
const publishIntent = publishIntentValue;
|
|
757
1085
|
let nameAvailability = "skipped";
|
|
758
1086
|
if (publishIntent) {
|
|
759
1087
|
nameAvailability = await pack.registry.checkNameAvailability(projectName, registryUrl);
|
|
760
1088
|
while (nameAvailability === "taken") {
|
|
761
1089
|
log.warn(`"${projectName}" is already taken on ${registryUrl}. Please choose a different name.`);
|
|
762
|
-
const retryValue = await
|
|
1090
|
+
const retryValue = await text2({
|
|
763
1091
|
message: "Project name",
|
|
764
1092
|
validate: pack.validateProjectName
|
|
765
1093
|
});
|
|
766
|
-
|
|
1094
|
+
exitIfCancelled2(retryValue);
|
|
767
1095
|
projectName = retryValue;
|
|
768
1096
|
nameAvailability = await pack.registry.checkNameAvailability(projectName, registryUrl);
|
|
769
1097
|
}
|
|
@@ -776,15 +1104,15 @@ async function runWizard(deps = defaultDeps) {
|
|
|
776
1104
|
}
|
|
777
1105
|
|
|
778
1106
|
// src/scaffold.ts
|
|
779
|
-
import { cp, readdir as
|
|
780
|
-
import
|
|
781
|
-
import
|
|
1107
|
+
import { cp, readdir as readdir4, readFile as readFile8, rename, writeFile as writeFile11 } from "fs/promises";
|
|
1108
|
+
import path15 from "path";
|
|
1109
|
+
import spawn2 from "cross-spawn";
|
|
782
1110
|
|
|
783
1111
|
// src/update/manifest.ts
|
|
784
1112
|
import { createHash } from "crypto";
|
|
785
1113
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
786
|
-
import { mkdir as
|
|
787
|
-
import
|
|
1114
|
+
import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile10 } from "fs/promises";
|
|
1115
|
+
import path14 from "path";
|
|
788
1116
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
789
1117
|
|
|
790
1118
|
// src/errors.ts
|
|
@@ -802,7 +1130,7 @@ function hashContent(content) {
|
|
|
802
1130
|
async function hashCoreFiles(dir, adapter) {
|
|
803
1131
|
const entries = await Promise.all(
|
|
804
1132
|
adapter.coreFilePaths.map(async (relativePath) => {
|
|
805
|
-
const content = await
|
|
1133
|
+
const content = await readFile7(path14.join(dir, relativePath), "utf8");
|
|
806
1134
|
return [relativePath, hashContent(content)];
|
|
807
1135
|
})
|
|
808
1136
|
);
|
|
@@ -814,15 +1142,15 @@ async function buildManifest(targetDir, generatorVersion, language, adapter) {
|
|
|
814
1142
|
const { coreDependencies, coreScripts, coreFields } = adapter.extractCoreFields(manifestFile);
|
|
815
1143
|
return { generatorVersion, language, coreFiles, coreDependencies, coreScripts, coreFields };
|
|
816
1144
|
}
|
|
817
|
-
var MANIFEST_RELATIVE_PATH =
|
|
1145
|
+
var MANIFEST_RELATIVE_PATH = path14.join(".clispark", "manifest.json");
|
|
818
1146
|
async function writeManifest(targetDir, manifest) {
|
|
819
|
-
const manifestPath =
|
|
820
|
-
await
|
|
821
|
-
await
|
|
1147
|
+
const manifestPath = path14.join(targetDir, MANIFEST_RELATIVE_PATH);
|
|
1148
|
+
await mkdir6(path14.dirname(manifestPath), { recursive: true });
|
|
1149
|
+
await writeFile10(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
822
1150
|
}
|
|
823
1151
|
async function readManifest(targetDir) {
|
|
824
1152
|
try {
|
|
825
|
-
const content = await
|
|
1153
|
+
const content = await readFile7(path14.join(targetDir, MANIFEST_RELATIVE_PATH), "utf8");
|
|
826
1154
|
return JSON.parse(content);
|
|
827
1155
|
} catch {
|
|
828
1156
|
return void 0;
|
|
@@ -838,14 +1166,14 @@ async function requireManifest(targetDir) {
|
|
|
838
1166
|
return manifest;
|
|
839
1167
|
}
|
|
840
1168
|
function getGeneratorVersion() {
|
|
841
|
-
let dir =
|
|
1169
|
+
let dir = path14.dirname(fileURLToPath2(import.meta.url));
|
|
842
1170
|
while (true) {
|
|
843
|
-
const pkgPath =
|
|
1171
|
+
const pkgPath = path14.join(dir, "package.json");
|
|
844
1172
|
if (existsSync2(pkgPath)) {
|
|
845
1173
|
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
846
1174
|
if (pkg.name === "clispark") return pkg.version;
|
|
847
1175
|
}
|
|
848
|
-
const parentDir =
|
|
1176
|
+
const parentDir = path14.dirname(dir);
|
|
849
1177
|
if (parentDir === dir) {
|
|
850
1178
|
throw new Error("Could not locate clispark's own package.json.");
|
|
851
1179
|
}
|
|
@@ -857,7 +1185,7 @@ function getGeneratorVersion() {
|
|
|
857
1185
|
async function assertTargetDirIsUsable(targetDir) {
|
|
858
1186
|
let entries;
|
|
859
1187
|
try {
|
|
860
|
-
entries = await
|
|
1188
|
+
entries = await readdir4(targetDir);
|
|
861
1189
|
} catch {
|
|
862
1190
|
return;
|
|
863
1191
|
}
|
|
@@ -869,10 +1197,10 @@ function applyPlaceholders(content, projectName) {
|
|
|
869
1197
|
return content.replaceAll("{{projectName}}", projectName);
|
|
870
1198
|
}
|
|
871
1199
|
async function collectFiles(dir) {
|
|
872
|
-
const entries = await
|
|
1200
|
+
const entries = await readdir4(dir, { withFileTypes: true });
|
|
873
1201
|
const files = await Promise.all(
|
|
874
1202
|
entries.map((entry) => {
|
|
875
|
-
const fullPath =
|
|
1203
|
+
const fullPath = path15.join(dir, entry.name);
|
|
876
1204
|
return entry.isDirectory() ? collectFiles(fullPath) : Promise.resolve([fullPath]);
|
|
877
1205
|
})
|
|
878
1206
|
);
|
|
@@ -882,9 +1210,9 @@ async function replacePlaceholdersInTree(targetDir, projectName) {
|
|
|
882
1210
|
const files = await collectFiles(targetDir);
|
|
883
1211
|
await Promise.all(
|
|
884
1212
|
files.map(async (filePath) => {
|
|
885
|
-
const content = await
|
|
1213
|
+
const content = await readFile8(filePath, "utf8");
|
|
886
1214
|
if (content.includes("{{projectName}}")) {
|
|
887
|
-
await
|
|
1215
|
+
await writeFile11(filePath, applyPlaceholders(content, projectName));
|
|
888
1216
|
}
|
|
889
1217
|
})
|
|
890
1218
|
);
|
|
@@ -893,7 +1221,7 @@ async function copyTemplate(options, pack) {
|
|
|
893
1221
|
const { projectName, targetDir, registryUrl, publishIntent } = options;
|
|
894
1222
|
await assertTargetDirIsUsable(targetDir);
|
|
895
1223
|
await cp(pack.templateDir, targetDir, { recursive: true });
|
|
896
|
-
await rename(
|
|
1224
|
+
await rename(path15.join(targetDir, "gitignore"), path15.join(targetDir, ".gitignore"));
|
|
897
1225
|
await replacePlaceholdersInTree(targetDir, projectName);
|
|
898
1226
|
if (publishIntent === false) {
|
|
899
1227
|
await pack.registry.applyPrivateIntent(targetDir);
|
|
@@ -904,7 +1232,7 @@ async function copyTemplate(options, pack) {
|
|
|
904
1232
|
}
|
|
905
1233
|
async function defaultRunCommand(command, args, cwd) {
|
|
906
1234
|
await new Promise((resolve, reject) => {
|
|
907
|
-
const child =
|
|
1235
|
+
const child = spawn2(command, args, { cwd, stdio: "inherit" });
|
|
908
1236
|
child.on("error", reject);
|
|
909
1237
|
child.on("close", (code) => {
|
|
910
1238
|
if (code === 0) {
|
|
@@ -932,7 +1260,7 @@ async function scaffoldProject(options, pack, deps = defaultScaffoldDeps) {
|
|
|
932
1260
|
// src/logger.ts
|
|
933
1261
|
import { randomBytes } from "crypto";
|
|
934
1262
|
import { mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
935
|
-
import
|
|
1263
|
+
import path16 from "path";
|
|
936
1264
|
import envPaths from "env-paths";
|
|
937
1265
|
import pino from "pino";
|
|
938
1266
|
var paths = envPaths("clispark", { suffix: "" });
|
|
@@ -968,7 +1296,7 @@ var SWEEP_MARKER_FILE = ".last-sweep";
|
|
|
968
1296
|
var SWEEP_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
969
1297
|
function sweepOldLogs(logDir) {
|
|
970
1298
|
safely(() => {
|
|
971
|
-
const markerPath =
|
|
1299
|
+
const markerPath = path16.join(logDir, SWEEP_MARKER_FILE);
|
|
972
1300
|
let shouldSweep = true;
|
|
973
1301
|
try {
|
|
974
1302
|
shouldSweep = Date.now() - statSync(markerPath).mtimeMs >= SWEEP_THROTTLE_MS;
|
|
@@ -978,7 +1306,7 @@ function sweepOldLogs(logDir) {
|
|
|
978
1306
|
const cutoffMs = Date.now() - getRetentionDays() * 24 * 60 * 60 * 1e3;
|
|
979
1307
|
for (const file of readdirSync(logDir)) {
|
|
980
1308
|
if (file === SWEEP_MARKER_FILE) continue;
|
|
981
|
-
const filePath =
|
|
1309
|
+
const filePath = path16.join(logDir, file);
|
|
982
1310
|
if (statSync(filePath).mtimeMs < cutoffMs) {
|
|
983
1311
|
unlinkSync(filePath);
|
|
984
1312
|
}
|
|
@@ -989,7 +1317,7 @@ function sweepOldLogs(logDir) {
|
|
|
989
1317
|
function createLogger(commandName, logDir = paths.log) {
|
|
990
1318
|
mkdirSync(logDir, { recursive: true });
|
|
991
1319
|
sweepOldLogs(logDir);
|
|
992
|
-
const logFilePath =
|
|
1320
|
+
const logFilePath = path16.join(logDir, buildLogFileName(commandName));
|
|
993
1321
|
const fileDestination = pino.destination({ dest: logFilePath, sync: true, mode: 384 });
|
|
994
1322
|
const destination = process.env.DEBUG ? pino.multistream([{ stream: fileDestination }, { stream: process.stdout }]) : fileDestination;
|
|
995
1323
|
const logger = pino({ redact: buildRedactPaths([...SENSITIVE_LOG_KEYS, "registryUrl"]) }, destination);
|
|
@@ -1027,9 +1355,9 @@ function withLogging(commandName, action, logDir = paths.log, loggerFactory = cr
|
|
|
1027
1355
|
}
|
|
1028
1356
|
|
|
1029
1357
|
// src/update/update.ts
|
|
1030
|
-
import { mkdir as
|
|
1031
|
-
import
|
|
1032
|
-
import
|
|
1358
|
+
import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile12 } from "fs/promises";
|
|
1359
|
+
import path17 from "path";
|
|
1360
|
+
import spawn3 from "cross-spawn";
|
|
1033
1361
|
|
|
1034
1362
|
// src/update/releasenotes.ts
|
|
1035
1363
|
function stripV(version) {
|
|
@@ -1046,7 +1374,7 @@ function compareVersions(a, b) {
|
|
|
1046
1374
|
return 0;
|
|
1047
1375
|
}
|
|
1048
1376
|
var RELEASES_URL = "https://api.github.com/repos/martinwichner/clispark/releases";
|
|
1049
|
-
var
|
|
1377
|
+
var FETCH_TIMEOUT_MS4 = 5e3;
|
|
1050
1378
|
async function fetchReleaseNotes(targetDir, fetchFn = fetch) {
|
|
1051
1379
|
const manifest = await requireManifest(targetDir);
|
|
1052
1380
|
const fromVersion = manifest.generatorVersion;
|
|
@@ -1054,7 +1382,7 @@ async function fetchReleaseNotes(targetDir, fetchFn = fetch) {
|
|
|
1054
1382
|
if (compareVersions(fromVersion, toVersion) >= 0) {
|
|
1055
1383
|
return { status: "up-to-date", fromVersion, toVersion, releases: [] };
|
|
1056
1384
|
}
|
|
1057
|
-
const response = await fetchFn(RELEASES_URL, { signal: AbortSignal.timeout(
|
|
1385
|
+
const response = await fetchFn(RELEASES_URL, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS4) });
|
|
1058
1386
|
if (!response.ok) {
|
|
1059
1387
|
throw new Error(`Failed to fetch release notes: GitHub API responded with ${response.status}`);
|
|
1060
1388
|
}
|
|
@@ -1077,7 +1405,7 @@ ${r.body}`).join("\n\n");
|
|
|
1077
1405
|
// src/update/update.ts
|
|
1078
1406
|
async function defaultRunCommand2(command, args, cwd) {
|
|
1079
1407
|
await new Promise((resolve, reject) => {
|
|
1080
|
-
const child =
|
|
1408
|
+
const child = spawn3(command, args, { cwd, stdio: "inherit" });
|
|
1081
1409
|
child.on("error", reject);
|
|
1082
1410
|
child.on("close", (code) => {
|
|
1083
1411
|
if (code === 0) resolve();
|
|
@@ -1087,7 +1415,7 @@ async function defaultRunCommand2(command, args, cwd) {
|
|
|
1087
1415
|
}
|
|
1088
1416
|
async function defaultCaptureCommand(command, args, cwd) {
|
|
1089
1417
|
return new Promise((resolve, reject) => {
|
|
1090
|
-
const child =
|
|
1418
|
+
const child = spawn3(command, args, { cwd });
|
|
1091
1419
|
let stdout = "";
|
|
1092
1420
|
child.stdout?.on("data", (chunk) => {
|
|
1093
1421
|
stdout += chunk.toString();
|
|
@@ -1114,7 +1442,7 @@ async function updateProject(targetDir, adapter, templateDir, language, deps = d
|
|
|
1114
1442
|
const currentManifestFile = await adapter.readManifestFile(targetDir);
|
|
1115
1443
|
const projectName = adapter.readProjectName(currentManifestFile);
|
|
1116
1444
|
const newTemplateRaw = applyPlaceholders(
|
|
1117
|
-
await
|
|
1445
|
+
await readFile9(path17.join(templateDir, adapter.manifestFileName), "utf8"),
|
|
1118
1446
|
projectName
|
|
1119
1447
|
);
|
|
1120
1448
|
const newTemplateManifestFile = adapter.parseManifestFile(newTemplateRaw);
|
|
@@ -1124,13 +1452,13 @@ async function updateProject(targetDir, adapter, templateDir, language, deps = d
|
|
|
1124
1452
|
const perFileResults = await Promise.all(
|
|
1125
1453
|
adapter.coreFilePaths.map(async (relativePath) => {
|
|
1126
1454
|
const newContent = applyPlaceholders(
|
|
1127
|
-
await
|
|
1455
|
+
await readFile9(path17.join(templateDir, adapter.templateSourcePath(relativePath)), "utf8"),
|
|
1128
1456
|
projectName
|
|
1129
1457
|
);
|
|
1130
1458
|
const newHash = hashContent(newContent);
|
|
1131
1459
|
let currentHash;
|
|
1132
1460
|
try {
|
|
1133
|
-
currentHash = hashContent(await
|
|
1461
|
+
currentHash = hashContent(await readFile9(path17.join(targetDir, relativePath), "utf8"));
|
|
1134
1462
|
} catch {
|
|
1135
1463
|
currentHash = void 0;
|
|
1136
1464
|
}
|
|
@@ -1142,13 +1470,13 @@ async function updateProject(targetDir, adapter, templateDir, language, deps = d
|
|
|
1142
1470
|
files.push({ path: relativePath, outcome: result.outcome });
|
|
1143
1471
|
newCoreFiles[relativePath] = result.value;
|
|
1144
1472
|
if (result.outcome === "added" || result.outcome === "replaced") {
|
|
1145
|
-
fileWrites.push({ targetPath:
|
|
1473
|
+
fileWrites.push({ targetPath: path17.join(targetDir, relativePath), content: newContent });
|
|
1146
1474
|
}
|
|
1147
1475
|
}
|
|
1148
1476
|
for (const relativePath of Object.keys(oldManifest.coreFiles)) {
|
|
1149
1477
|
if (adapter.coreFilePaths.includes(relativePath)) continue;
|
|
1150
1478
|
try {
|
|
1151
|
-
await
|
|
1479
|
+
await readFile9(path17.join(targetDir, relativePath), "utf8");
|
|
1152
1480
|
files.push({ path: relativePath, outcome: "no-longer-core" });
|
|
1153
1481
|
} catch {
|
|
1154
1482
|
}
|
|
@@ -1170,8 +1498,8 @@ async function updateProject(targetDir, adapter, templateDir, language, deps = d
|
|
|
1170
1498
|
};
|
|
1171
1499
|
}
|
|
1172
1500
|
for (const write of fileWrites) {
|
|
1173
|
-
await
|
|
1174
|
-
await
|
|
1501
|
+
await mkdir7(path17.dirname(write.targetPath), { recursive: true });
|
|
1502
|
+
await writeFile12(write.targetPath, write.content);
|
|
1175
1503
|
}
|
|
1176
1504
|
if (fileMerge.changed) {
|
|
1177
1505
|
await adapter.writeManifestFile(targetDir, fileMerge.updatedFile);
|
|
@@ -1234,10 +1562,10 @@ function formatUpdateSummary(result) {
|
|
|
1234
1562
|
}
|
|
1235
1563
|
|
|
1236
1564
|
// src/add-wizard.ts
|
|
1237
|
-
import { intro as intro2, outro as outro2, select as
|
|
1238
|
-
function
|
|
1239
|
-
if (
|
|
1240
|
-
|
|
1565
|
+
import { intro as intro2, outro as outro2, select as select3, text as text3, confirm, isCancel as isCancel3, cancel as cancel3, log as log2 } from "@clack/prompts";
|
|
1566
|
+
function exitIfCancelled3(value) {
|
|
1567
|
+
if (isCancel3(value)) {
|
|
1568
|
+
cancel3("Operation cancelled.");
|
|
1241
1569
|
process.exit(1);
|
|
1242
1570
|
}
|
|
1243
1571
|
}
|
|
@@ -1267,8 +1595,8 @@ async function selectWithinNode(node) {
|
|
|
1267
1595
|
{ value: HERE, label: `Direct subcommand of "${node.path}"` },
|
|
1268
1596
|
...node.children.map((child) => ({ value: child.path, label: `Under "${child.path}"` }))
|
|
1269
1597
|
];
|
|
1270
|
-
const choice = await
|
|
1271
|
-
|
|
1598
|
+
const choice = await select3({ message: `Where under "${node.path}"?`, options });
|
|
1599
|
+
exitIfCancelled3(choice);
|
|
1272
1600
|
if (choice === HERE) {
|
|
1273
1601
|
return node.path.split(" ");
|
|
1274
1602
|
}
|
|
@@ -1277,8 +1605,8 @@ async function selectWithinNode(node) {
|
|
|
1277
1605
|
}
|
|
1278
1606
|
async function selectPath(existing) {
|
|
1279
1607
|
const options = [{ value: NEW_TOP_LEVEL, label: "New top-level command" }, ...flattenForMenu(existing)];
|
|
1280
|
-
const choice = await
|
|
1281
|
-
|
|
1608
|
+
const choice = await select3({ message: "Where should the new command go?", options });
|
|
1609
|
+
exitIfCancelled3(choice);
|
|
1282
1610
|
if (choice === NEW_TOP_LEVEL) {
|
|
1283
1611
|
return [];
|
|
1284
1612
|
}
|
|
@@ -1295,9 +1623,9 @@ async function collectParameters() {
|
|
|
1295
1623
|
const addMore = await confirm({
|
|
1296
1624
|
message: parameters.length === 0 ? "Add a parameter?" : "Add another parameter?"
|
|
1297
1625
|
});
|
|
1298
|
-
|
|
1626
|
+
exitIfCancelled3(addMore);
|
|
1299
1627
|
if (!addMore) break;
|
|
1300
|
-
const nameValue = await
|
|
1628
|
+
const nameValue = await text3({
|
|
1301
1629
|
message: "Parameter name",
|
|
1302
1630
|
validate: (value) => {
|
|
1303
1631
|
if (!/^[a-z][a-zA-Z0-9]*$/.test(value)) return "Use a single word starting with a lowercase letter.";
|
|
@@ -1305,9 +1633,9 @@ async function collectParameters() {
|
|
|
1305
1633
|
return void 0;
|
|
1306
1634
|
}
|
|
1307
1635
|
});
|
|
1308
|
-
|
|
1636
|
+
exitIfCancelled3(nameValue);
|
|
1309
1637
|
const name = nameValue;
|
|
1310
|
-
const typeValue = await
|
|
1638
|
+
const typeValue = await select3({
|
|
1311
1639
|
message: "Parameter type",
|
|
1312
1640
|
options: [
|
|
1313
1641
|
{ value: "string", label: "String" },
|
|
@@ -1316,7 +1644,7 @@ async function collectParameters() {
|
|
|
1316
1644
|
{ value: "enum", label: "String with allowed values" }
|
|
1317
1645
|
]
|
|
1318
1646
|
});
|
|
1319
|
-
|
|
1647
|
+
exitIfCancelled3(typeValue);
|
|
1320
1648
|
const type = typeValue;
|
|
1321
1649
|
let required = false;
|
|
1322
1650
|
if (type !== "boolean") {
|
|
@@ -1324,14 +1652,14 @@ async function collectParameters() {
|
|
|
1324
1652
|
{ value: true, label: "Required" },
|
|
1325
1653
|
{ value: false, label: "Optional" }
|
|
1326
1654
|
];
|
|
1327
|
-
const requiredValue = await
|
|
1328
|
-
|
|
1655
|
+
const requiredValue = await select3({ message: "Required or optional?", options });
|
|
1656
|
+
exitIfCancelled3(requiredValue);
|
|
1329
1657
|
required = requiredValue;
|
|
1330
1658
|
}
|
|
1331
1659
|
if (!required) hasOptional = true;
|
|
1332
1660
|
let allowedValues;
|
|
1333
1661
|
if (type === "enum") {
|
|
1334
|
-
const valuesInput = await
|
|
1662
|
+
const valuesInput = await text3({
|
|
1335
1663
|
message: "Allowed values (comma-separated)",
|
|
1336
1664
|
validate: (value) => {
|
|
1337
1665
|
const values = value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
@@ -1344,7 +1672,7 @@ async function collectParameters() {
|
|
|
1344
1672
|
return void 0;
|
|
1345
1673
|
}
|
|
1346
1674
|
});
|
|
1347
|
-
|
|
1675
|
+
exitIfCancelled3(valuesInput);
|
|
1348
1676
|
allowedValues = valuesInput.split(",").map((v) => v.trim()).filter(Boolean);
|
|
1349
1677
|
}
|
|
1350
1678
|
parameters.push({ name, type, required, allowedValues });
|
|
@@ -1356,23 +1684,28 @@ async function runAddWizard(targetDir, deps) {
|
|
|
1356
1684
|
const existing = await deps.commandGenerator.listExistingCommands(targetDir);
|
|
1357
1685
|
const pathSegments = await selectPath(existing);
|
|
1358
1686
|
const existingPaths = new Set(flattenPaths(existing));
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1687
|
+
let fullPathSegments;
|
|
1688
|
+
if (deps.commandGenerator.promptCommandIdentity) {
|
|
1689
|
+
fullPathSegments = await deps.commandGenerator.promptCommandIdentity(pathSegments, existingPaths);
|
|
1690
|
+
} else {
|
|
1691
|
+
const nameValue = await text3({
|
|
1692
|
+
message: "Command name",
|
|
1693
|
+
validate: (value) => {
|
|
1694
|
+
if (!/^[a-z][a-zA-Z0-9]*$/.test(value)) return "Use a single word starting with a lowercase letter.";
|
|
1695
|
+
const fullPath = [...pathSegments, value].join(" ");
|
|
1696
|
+
if (existingPaths.has(fullPath)) return `"${fullPath}" already exists.`;
|
|
1697
|
+
return void 0;
|
|
1698
|
+
}
|
|
1699
|
+
});
|
|
1700
|
+
exitIfCancelled3(nameValue);
|
|
1701
|
+
fullPathSegments = [...pathSegments, nameValue];
|
|
1702
|
+
}
|
|
1370
1703
|
const parameters = await collectParameters();
|
|
1371
1704
|
log2.info(`About to create "${fullPathSegments.join(" ")}" with ${parameters.length} parameter(s).`);
|
|
1372
1705
|
const proceed = await confirm({ message: "Proceed?" });
|
|
1373
|
-
|
|
1706
|
+
exitIfCancelled3(proceed);
|
|
1374
1707
|
if (!proceed) {
|
|
1375
|
-
|
|
1708
|
+
cancel3("Cancelled.");
|
|
1376
1709
|
process.exit(1);
|
|
1377
1710
|
}
|
|
1378
1711
|
const spec = { pathSegments: fullPathSegments, parameters };
|
|
@@ -1383,7 +1716,7 @@ async function runAddWizard(targetDir, deps) {
|
|
|
1383
1716
|
// src/whoami.ts
|
|
1384
1717
|
import os from "os";
|
|
1385
1718
|
var JOKE_API_URL = "https://v2.jokeapi.dev/joke/Programming,Miscellaneous";
|
|
1386
|
-
var
|
|
1719
|
+
var FETCH_TIMEOUT_MS5 = 3e3;
|
|
1387
1720
|
var SUPPORTED_JOKE_LANGUAGES = ["cs", "de", "en", "es", "fr", "pt"];
|
|
1388
1721
|
var LOGO = String.raw`
|
|
1389
1722
|
⚡ clispark
|
|
@@ -1441,7 +1774,7 @@ function getRandomFunFact(osFacts = defaultOsFacts, randomFn = Math.random) {
|
|
|
1441
1774
|
async function fetchJoke(language, fetchFn) {
|
|
1442
1775
|
try {
|
|
1443
1776
|
const url = `${JOKE_API_URL}?lang=${language}&safe-mode`;
|
|
1444
|
-
const response = await fetchFn(url, { signal: AbortSignal.timeout(
|
|
1777
|
+
const response = await fetchFn(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS5) });
|
|
1445
1778
|
if (!response.ok) return void 0;
|
|
1446
1779
|
const data = await response.json();
|
|
1447
1780
|
if (data.error) return void 0;
|
|
@@ -1483,11 +1816,11 @@ ${getConfetti(randomFn)}
|
|
|
1483
1816
|
|
|
1484
1817
|
// src/hooks.ts
|
|
1485
1818
|
import { existsSync as existsSync3 } from "fs";
|
|
1486
|
-
import
|
|
1819
|
+
import path18 from "path";
|
|
1487
1820
|
import { pathToFileURL } from "url";
|
|
1488
1821
|
import envPaths2 from "env-paths";
|
|
1489
1822
|
function getPostScaffoldHookPath(configDir = envPaths2("clispark", { suffix: "" }).config) {
|
|
1490
|
-
return
|
|
1823
|
+
return path18.join(configDir, "hooks", "post-scaffold.mjs");
|
|
1491
1824
|
}
|
|
1492
1825
|
var defaultDeps2 = {
|
|
1493
1826
|
fileExists: existsSync3,
|
|
@@ -1530,7 +1863,7 @@ function resolvePack(language) {
|
|
|
1530
1863
|
program.action(
|
|
1531
1864
|
(options) => withLogging("scaffold", async (logger) => {
|
|
1532
1865
|
const answers = await runWizard();
|
|
1533
|
-
const targetDir =
|
|
1866
|
+
const targetDir = path19.join(process.cwd(), answers.projectName);
|
|
1534
1867
|
const pack = resolvePack(answers.language);
|
|
1535
1868
|
logger.info({ projectName: answers.projectName, targetDir, language: pack.id }, "scaffold started");
|
|
1536
1869
|
await scaffoldProject(
|