clispark 1.17.1 → 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 CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import path14 from "path";
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 exitIfCancelled(value) {
708
- if (isCancel(value)) {
709
- cancel("Operation cancelled.");
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 select({
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
- exitIfCancelled(languageValue);
1048
+ exitIfCancelled2(languageValue);
721
1049
  const pack = deps.languagePacks[languageValue];
722
- const nameValue = await text({
1050
+ const nameValue = await text2({
723
1051
  message: "Project name",
724
1052
  validate: pack.validateProjectName
725
1053
  });
726
- exitIfCancelled(nameValue);
1054
+ exitIfCancelled2(nameValue);
727
1055
  let projectName = nameValue;
728
- const profileValue = await select({
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
- exitIfCancelled(profileValue);
1063
+ exitIfCancelled2(profileValue);
736
1064
  const profile = profileValue;
737
1065
  let registryUrl = pack.registry.defaultUrl;
738
1066
  if (profile === "work") {
739
- const registryValue = await text({
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
- exitIfCancelled(registryValue);
1072
+ exitIfCancelled2(registryValue);
745
1073
  registryUrl = registryValue || pack.registry.defaultUrl;
746
1074
  }
747
- const publishIntentValue = await select({
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
- exitIfCancelled(publishIntentValue);
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 text({
1090
+ const retryValue = await text2({
763
1091
  message: "Project name",
764
1092
  validate: pack.validateProjectName
765
1093
  });
766
- exitIfCancelled(retryValue);
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 readdir3, readFile as readFile7, rename, writeFile as writeFile8 } from "fs/promises";
780
- import path11 from "path";
781
- import spawn from "cross-spawn";
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 mkdir4, readFile as readFile6, writeFile as writeFile7 } from "fs/promises";
787
- import path10 from "path";
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 readFile6(path10.join(dir, relativePath), "utf8");
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 = path10.join(".clispark", "manifest.json");
1145
+ var MANIFEST_RELATIVE_PATH = path14.join(".clispark", "manifest.json");
818
1146
  async function writeManifest(targetDir, manifest) {
819
- const manifestPath = path10.join(targetDir, MANIFEST_RELATIVE_PATH);
820
- await mkdir4(path10.dirname(manifestPath), { recursive: true });
821
- await writeFile7(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
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 readFile6(path10.join(targetDir, MANIFEST_RELATIVE_PATH), "utf8");
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 = path10.dirname(fileURLToPath2(import.meta.url));
1169
+ let dir = path14.dirname(fileURLToPath2(import.meta.url));
842
1170
  while (true) {
843
- const pkgPath = path10.join(dir, "package.json");
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 = path10.dirname(dir);
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 readdir3(targetDir);
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 readdir3(dir, { withFileTypes: true });
1200
+ const entries = await readdir4(dir, { withFileTypes: true });
873
1201
  const files = await Promise.all(
874
1202
  entries.map((entry) => {
875
- const fullPath = path11.join(dir, entry.name);
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 readFile7(filePath, "utf8");
1213
+ const content = await readFile8(filePath, "utf8");
886
1214
  if (content.includes("{{projectName}}")) {
887
- await writeFile8(filePath, applyPlaceholders(content, projectName));
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(path11.join(targetDir, "gitignore"), path11.join(targetDir, ".gitignore"));
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 = spawn(command, args, { cwd, stdio: "inherit" });
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 path12 from "path";
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 = path12.join(logDir, SWEEP_MARKER_FILE);
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 = path12.join(logDir, file);
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 = path12.join(logDir, buildLogFileName(commandName));
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 mkdir5, readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
1031
- import path13 from "path";
1032
- import spawn2 from "cross-spawn";
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 FETCH_TIMEOUT_MS3 = 5e3;
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(FETCH_TIMEOUT_MS3) });
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 = spawn2(command, args, { cwd, stdio: "inherit" });
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 = spawn2(command, args, { cwd });
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 readFile8(path13.join(templateDir, adapter.manifestFileName), "utf8"),
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 readFile8(path13.join(templateDir, adapter.templateSourcePath(relativePath)), "utf8"),
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 readFile8(path13.join(targetDir, relativePath), "utf8"));
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: path13.join(targetDir, relativePath), content: newContent });
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 readFile8(path13.join(targetDir, relativePath), "utf8");
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 mkdir5(path13.dirname(write.targetPath), { recursive: true });
1174
- await writeFile9(write.targetPath, write.content);
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 select2, text as text2, confirm, isCancel as isCancel2, cancel as cancel2, log as log2 } from "@clack/prompts";
1238
- function exitIfCancelled2(value) {
1239
- if (isCancel2(value)) {
1240
- cancel2("Operation cancelled.");
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 select2({ message: `Where under "${node.path}"?`, options });
1271
- exitIfCancelled2(choice);
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 select2({ message: "Where should the new command go?", options });
1281
- exitIfCancelled2(choice);
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
- exitIfCancelled2(addMore);
1626
+ exitIfCancelled3(addMore);
1299
1627
  if (!addMore) break;
1300
- const nameValue = await text2({
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
- exitIfCancelled2(nameValue);
1636
+ exitIfCancelled3(nameValue);
1309
1637
  const name = nameValue;
1310
- const typeValue = await select2({
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
- exitIfCancelled2(typeValue);
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 select2({ message: "Required or optional?", options });
1328
- exitIfCancelled2(requiredValue);
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 text2({
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
- exitIfCancelled2(valuesInput);
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
- const nameValue = await text2({
1360
- message: "Command name",
1361
- validate: (value) => {
1362
- if (!/^[a-z][a-zA-Z0-9]*$/.test(value)) return "Use a single word starting with a lowercase letter.";
1363
- const fullPath = [...pathSegments, value].join(" ");
1364
- if (existingPaths.has(fullPath)) return `"${fullPath}" already exists.`;
1365
- return void 0;
1366
- }
1367
- });
1368
- exitIfCancelled2(nameValue);
1369
- const fullPathSegments = [...pathSegments, nameValue];
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
- exitIfCancelled2(proceed);
1706
+ exitIfCancelled3(proceed);
1374
1707
  if (!proceed) {
1375
- cancel2("Cancelled.");
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 FETCH_TIMEOUT_MS4 = 3e3;
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(FETCH_TIMEOUT_MS4) });
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;
@@ -1481,9 +1814,45 @@ ${getConfetti(randomFn)}
1481
1814
  `);
1482
1815
  }
1483
1816
 
1817
+ // src/hooks.ts
1818
+ import { existsSync as existsSync3 } from "fs";
1819
+ import path18 from "path";
1820
+ import { pathToFileURL } from "url";
1821
+ import envPaths2 from "env-paths";
1822
+ function getPostScaffoldHookPath(configDir = envPaths2("clispark", { suffix: "" }).config) {
1823
+ return path18.join(configDir, "hooks", "post-scaffold.mjs");
1824
+ }
1825
+ var defaultDeps2 = {
1826
+ fileExists: existsSync3,
1827
+ importHookModule: (fileUrl) => import(fileUrl),
1828
+ warn: (message) => console.warn(message)
1829
+ };
1830
+ function warnHookFailed(deps, detail) {
1831
+ deps.warn(
1832
+ `\u26A0 Post-scaffold hook failed: ${detail}
1833
+ Your project was still created successfully \u2014 this only affects the optional hook.`
1834
+ );
1835
+ }
1836
+ async function runPostScaffoldHook(context, hookPath = getPostScaffoldHookPath(), deps = defaultDeps2) {
1837
+ if (!deps.fileExists(hookPath)) {
1838
+ return;
1839
+ }
1840
+ try {
1841
+ const mod = await deps.importHookModule(pathToFileURL(hookPath).href);
1842
+ if (typeof mod.default !== "function") {
1843
+ throw new Error("post-scaffold.mjs must have a default export that is a function");
1844
+ }
1845
+ await mod.default(context);
1846
+ } catch (err) {
1847
+ const detail = err instanceof Error ? err.message : String(err);
1848
+ warnHookFailed(deps, detail);
1849
+ }
1850
+ }
1851
+
1484
1852
  // src/cli.ts
1853
+ import { existsSync as existsSync4 } from "fs";
1485
1854
  var program = new Command();
1486
- program.name("clispark").description("Interactive scaffolding tool for new CLI projects").option("--no-confetti", "Skip the confetti after a successful run").configureHelp({ showGlobalOptions: true }).version(getGeneratorVersion());
1855
+ program.name("clispark").description("Interactive scaffolding tool for new CLI projects").option("--no-confetti", "Skip the confetti after a successful run").option("--no-hook", "Skip the post-scaffold hook, even if one is configured").configureHelp({ showGlobalOptions: true }).version(getGeneratorVersion());
1487
1856
  function resolvePack(language) {
1488
1857
  const pack = LANGUAGE_PACKS[language];
1489
1858
  if (!pack) {
@@ -1494,7 +1863,7 @@ function resolvePack(language) {
1494
1863
  program.action(
1495
1864
  (options) => withLogging("scaffold", async (logger) => {
1496
1865
  const answers = await runWizard();
1497
- const targetDir = path14.join(process.cwd(), answers.projectName);
1866
+ const targetDir = path19.join(process.cwd(), answers.projectName);
1498
1867
  const pack = resolvePack(answers.language);
1499
1868
  logger.info({ projectName: answers.projectName, targetDir, language: pack.id }, "scaffold started");
1500
1869
  await scaffoldProject(
@@ -1509,6 +1878,15 @@ program.action(
1509
1878
  logger.info({ projectName: answers.projectName }, "scaffold completed");
1510
1879
  console.log(`
1511
1880
  Done! Your new CLI project is ready at ${targetDir}`);
1881
+ if (options.hook !== false) {
1882
+ await runPostScaffoldHook({
1883
+ projectName: answers.projectName,
1884
+ targetDir,
1885
+ language: pack.id,
1886
+ registryUrl: answers.registryUrl,
1887
+ publishIntent: answers.publishIntent
1888
+ });
1889
+ }
1512
1890
  if (options.confetti !== false) printConfetti();
1513
1891
  })()
1514
1892
  );
@@ -1562,6 +1940,23 @@ program.command("whoami").description("A little something extra").option("--joke
1562
1940
  logger.info({}, "whoami completed");
1563
1941
  })()
1564
1942
  );
1943
+ program.command("hook").description("Show the post-scaffold hook file location and whether one is configured").action(
1944
+ () => withLogging("hook", async (logger) => {
1945
+ const hookPath = getPostScaffoldHookPath();
1946
+ const exists = existsSync4(hookPath);
1947
+ logger.info({ hookPath, exists }, "hook status checked");
1948
+ console.log("\nPost-scaffold hook\n");
1949
+ console.log(`Location: ${hookPath}`);
1950
+ if (exists) {
1951
+ console.log("Status: found \u2014 will run after the next scaffold");
1952
+ } else {
1953
+ console.log("Status: not found \u2014 no hook will run after the next scaffold");
1954
+ console.log(
1955
+ "\nTo add one, create that file as an ES module exporting a default function:\n\n export default async function postScaffold({ projectName, targetDir, language, registryUrl, publishIntent }) {\n // your code here\n }\n\nIt runs once, right after a new project finishes scaffolding."
1956
+ );
1957
+ }
1958
+ })()
1959
+ );
1565
1960
  program.parseAsync(process.argv).catch((error) => {
1566
1961
  console.error(error);
1567
1962
  process.exit(1);