sv 0.9.14 → 0.10.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/bin.js CHANGED
@@ -1,21 +1,31 @@
1
1
  #!/usr/bin/env node
2
- import { B as We, F as Ge, H as ke, I as J, J as require_picocolors, K as up$1, L as Ke, N as De, O as isVersionUnsupportedBelow, P as Fe, R as T, U as ze, V as et, W as Vu, X as Option, Y as Command, Z as program, _ as parseScript, a as addPnpmBuildDependencies, c as installOption, et as __toESM, f as detect, g as parseJson, i as AGENT_NAMES, l as packageManagerPrompt, n as templates, o as getUserAgent, p as resolveCommand, q as from, r as dist, s as installDependencies, t as create$1, v as parseSvelte, z as Ue } from "./create-Bt2-1pFJ.js";
3
- import { a as formatFiles, c as getCommunityAddon, d as createDefault, f as addDefault, i as createWorkspace, l as getAddonDetails, m as overrideProperties, o as getHighlighter, p as create$2, r as setupAddons, s as communityAddonIds, t as applyAddons, u as officialAddons } from "./install-CisPUEH8.js";
2
+ import { $ as ke, C as getSharedFiles, D as parseJson, G as Fe, J as Ke, K as Ge, O as parseScript, Q as et, W as De, X as Ue, Y as T, Z as We, a as getUserAgent, at as program, b as detect, c as packageManagerPrompt, ct as __toESM, d as from, et as ze, f as commonFilePaths, h as getHighlighter, i as addPnpmBuildDependencies, it as Option, k as parseSvelte, m as formatFiles, n as templates, nt as require_picocolors, o as installDependencies, q as J, r as AGENT_NAMES, rt as Command, s as installOption, t as create$1, tt as Vu, u as up$1, w as resolveCommand, z as isVersionUnsupportedBelow } from "./create-BN4H6Ywy.js";
3
+ import { a as communityAddonIds, c as officialAddons, d as create$2, f as overrideProperties, i as createWorkspace, l as createDefault, o as getCommunityAddon, r as setupAddons, s as getAddonDetails, t as applyAddons, u as addDefault } from "./install-BeymWyuI.js";
4
4
  import { exec, execSync } from "node:child_process";
5
5
  import path, { dirname, join } from "node:path";
6
6
  import fs, { existsSync } from "node:fs";
7
7
  import process from "node:process";
8
- import { fileURLToPath } from "node:url";
9
8
  import { promisify } from "node:util";
9
+ import { fileURLToPath } from "node:url";
10
10
  import { createGunzip } from "node:zlib";
11
11
  import { pipeline } from "node:stream/promises";
12
12
 
13
13
  //#region package.json
14
14
  var name = "sv";
15
- var version = "0.9.14";
15
+ var version = "0.10.0";
16
16
 
17
17
  //#endregion
18
- //#region ../../node_modules/.pnpm/valibot@0.41.0_typescript@5.8.3/node_modules/valibot/dist/index.js
18
+ //#region ../../node_modules/.pnpm/empathic@1.1.0/node_modules/empathic/package.mjs
19
+ /**
20
+ * Find the closest "package.json" file while walking parent directories.
21
+ * @returns The absolute path to a "package.json", if found.
22
+ */
23
+ function up(options$1) {
24
+ return up$1("package.json", options$1);
25
+ }
26
+
27
+ //#endregion
28
+ //#region ../../node_modules/.pnpm/valibot@0.41.0_typescript@5.9.3/node_modules/valibot/dist/index.js
19
29
  var import_picocolors$3 = /* @__PURE__ */ __toESM(require_picocolors(), 1);
20
30
  var store;
21
31
  function getGlobalConfig(config2) {
@@ -428,16 +438,6 @@ function pipe(...pipe2) {
428
438
  };
429
439
  }
430
440
 
431
- //#endregion
432
- //#region ../../node_modules/.pnpm/empathic@1.1.0/node_modules/empathic/package.mjs
433
- /**
434
- * Find the closest "package.json" file while walking parent directories.
435
- * @returns The absolute path to a "package.json", if found.
436
- */
437
- function up(options$1) {
438
- return up$1("package.json", options$1);
439
- }
440
-
441
441
  //#endregion
442
442
  //#region utils/errors.ts
443
443
  var UnsupportedError = class extends Error {
@@ -532,6 +532,67 @@ function parseAddonOptions(optionFlags) {
532
532
  }
533
533
  return options$1;
534
534
  }
535
+ function logArgs(agent, actionName, args) {
536
+ const defaultArgs = [
537
+ "sv",
538
+ actionName,
539
+ ...args
540
+ ];
541
+ const res = resolveCommand(agent, "execute", defaultArgs);
542
+ if (res) T.message(import_picocolors$3.default.dim([res.command, ...res.args].join(" ")));
543
+ else T.message(import_picocolors$3.default.dim([`npx`, ...defaultArgs].join(" ")));
544
+ }
545
+ function errorAndExit(message) {
546
+ T.error(message);
547
+ De("Operation failed.");
548
+ process.exit(1);
549
+ }
550
+
551
+ //#endregion
552
+ //#region commands/add/verifiers.ts
553
+ function verifyCleanWorkingDirectory(cwd, gitCheck) {
554
+ const verifications = [];
555
+ if (gitCheck) verifications.push({
556
+ name: "clean working directory",
557
+ run: async () => {
558
+ try {
559
+ const { stdout: stdout$1 } = await promisify(exec)("git status --short", { cwd });
560
+ if (stdout$1) return {
561
+ success: false,
562
+ message: "Found modified files"
563
+ };
564
+ return {
565
+ success: true,
566
+ message: void 0
567
+ };
568
+ } catch {
569
+ return {
570
+ success: true,
571
+ message: "Not a git repository"
572
+ };
573
+ }
574
+ }
575
+ });
576
+ return verifications;
577
+ }
578
+ function verifyUnsupportedAddons(addons, addonSetupResult) {
579
+ const verifications = [];
580
+ verifications.push({
581
+ name: "unsupported add-ons",
582
+ run: () => {
583
+ const reasons = addons.flatMap((a) => addonSetupResult[a.id].unsupported.map((reason) => ({
584
+ id: a.id,
585
+ reason
586
+ })));
587
+ if (reasons.length === 0) return {
588
+ success: true,
589
+ message: void 0
590
+ };
591
+ throw new UnsupportedError(reasons);
592
+ }
593
+ });
594
+ return verifications;
595
+ }
535
596
 
536
597
  //#endregion
537
598
  //#region commands/add/fetch-packages.ts
@@ -612,52 +673,6 @@ async function fetchPackageJSON(packageName) {
612
673
  return await resp.json();
613
674
  }
614
675
 
615
- //#endregion
616
- //#region commands/add/verifiers.ts
617
- function verifyCleanWorkingDirectory(cwd, gitCheck) {
618
- const verifications = [];
619
- if (gitCheck) verifications.push({
620
- name: "clean working directory",
621
- run: async () => {
622
- try {
623
- const { stdout: stdout$1 } = await promisify(exec)("git status --short", { cwd });
624
- if (stdout$1) return {
625
- success: false,
626
- message: "Found modified files"
627
- };
628
- return {
629
- success: true,
630
- message: void 0
631
- };
632
- } catch {
633
- return {
634
- success: true,
635
- message: "Not a git repository"
636
- };
637
- }
638
- }
639
- });
640
- return verifications;
641
- }
642
- function verifyUnsupportedAddons(addons, addonSetupResult) {
643
- const verifications = [];
644
- verifications.push({
645
- name: "unsupported add-ons",
646
- run: () => {
647
- const reasons = addons.flatMap((a) => addonSetupResult[a.id].unsupported.map((reason) => ({
648
- id: a.id,
649
- reason
650
- })));
651
- if (reasons.length === 0) return {
652
- success: true,
653
- message: void 0
654
- };
655
- throw new UnsupportedError(reasons);
656
- }
657
- });
658
- return verifications;
659
- }
660
-
661
676
  //#endregion
662
677
  //#region commands/add/index.ts
663
678
  var import_picocolors$2 = /* @__PURE__ */ __toESM(require_picocolors(), 1);
@@ -675,24 +690,7 @@ const OptionsSchema$1 = strictObject({
675
690
  });
676
691
  const defaultPkgPath = up();
677
692
  const defaultCwd = defaultPkgPath ? path.dirname(defaultPkgPath) : void 0;
678
- const add = new Command("add").description("applies specified add-ons into a project").argument("[add-on...]", `add-ons to install`, (value, prev = []) => {
679
- const [addonId, optionFlags] = value.split("=", 2);
680
- if (prev.find(({ id }) => id === addonId)) {
681
- console.error(`Malformed arguments: Add-on '${addonId}' is repeated multiple times.`);
682
- process.exit(1);
683
- }
684
- try {
685
- const options$1 = parseAddonOptions(optionFlags);
686
- prev.push({
687
- id: addonId,
688
- options: options$1
689
- });
690
- } catch (error) {
691
- if (error instanceof Error) console.error(error.message);
692
- process.exit(1);
693
- }
694
- return prev;
695
- }).option("-C, --cwd <path>", "path to working directory", defaultCwd).option("--no-git-check", "even if some files are dirty, no prompt will be shown").option("--no-install", "skip installing dependencies").addOption(installOption).configureHelp({
693
+ const add = new Command("add").description("applies specified add-ons into a project").argument("[add-on...]", `add-ons to install`, (value, previous = []) => addonArgsHandler(previous, value)).option("-C, --cwd <path>", "path to working directory", defaultCwd).option("--no-git-check", "even if some files are dirty, no prompt will be shown").option("--no-install", "skip installing dependencies").addOption(installOption).configureHelp({
696
694
  ...helpConfig,
697
695
  formatHelp(cmd, helper) {
698
696
  const termWidth = helper.padWidth(cmd, helper);
@@ -748,57 +746,61 @@ const add = new Command("add").description("applies specified add-ons into a pro
748
746
  ]);
749
747
  return output.join("\n");
750
748
  }
751
- }).action((addonArgs, opts) => {
752
- if (opts.cwd === void 0) {
753
- console.error("Invalid workspace: Please verify that you are inside of a Svelte project. You can also specify the working directory with `--cwd <path>`");
754
- process.exit(1);
755
- } else if (!fs.existsSync(path.resolve(opts.cwd, "package.json"))) {
756
- console.error(`Invalid workspace: Path '${path.resolve(opts.cwd)}' is not a valid workspace.`);
757
- process.exit(1);
758
- }
759
- const addonIds = officialAddons$1.map((addon) => addon.id);
760
- const invalidAddons = addonArgs.filter(({ id }) => !addonIds.includes(id) && !aliases.includes(id)).map(({ id }) => id);
761
- if (invalidAddons.length > 0) {
762
- console.error(`Invalid add-ons specified: ${invalidAddons.join(", ")}`);
763
- process.exit(1);
764
- }
749
+ }).action(async (addonArgs, opts) => {
750
+ if (opts.cwd === void 0) errorAndExit("Invalid workspace: Please verify that you are inside of a Svelte project. You can also specify the working directory with `--cwd <path>`");
751
+ else if (!fs.existsSync(path.resolve(opts.cwd, "package.json"))) errorAndExit(`Invalid workspace: Path '${path.resolve(opts.cwd)}' is not a valid workspace.`);
752
+ const selectedAddonArgs = sanitizeAddons(addonArgs);
765
753
  const options$1 = parse(OptionsSchema$1, {
766
754
  ...opts,
767
755
  addons: {}
768
756
  });
769
- const selectedAddons = transformAliases(addonArgs);
770
- selectedAddons.forEach((addon) => options$1.addons[addon.id] = addon.options);
757
+ selectedAddonArgs.forEach((addon) => options$1.addons[addon.id] = addon.options);
758
+ const workspace = await createWorkspace({ cwd: options$1.cwd });
771
759
  runCommand(async () => {
772
- const { nextSteps } = await runAddCommand(options$1, selectedAddons.map(({ id }) => id));
760
+ const { answersCommunity, answersOfficial, selectedAddons } = await promptAddonQuestions({
761
+ options: options$1,
762
+ selectedAddonIds: selectedAddonArgs.map(({ id }) => id),
763
+ workspace
764
+ });
765
+ const { nextSteps } = await runAddonsApply({
766
+ answersOfficial,
767
+ answersCommunity,
768
+ options: options$1,
769
+ selectedAddons,
770
+ workspace,
771
+ withLogArgs: true
772
+ });
773
773
  if (nextSteps.length > 0) Ke(nextSteps.join("\n"), "Next steps", { format: (line) => line });
774
774
  });
775
775
  });
776
- async function runAddCommand(options$1, selectedAddonIds) {
777
- let selectedAddons = selectedAddonIds.map((id) => ({
778
- type: "official",
779
- addon: getAddonDetails(id)
780
- }));
781
- const official = {};
782
- const community = {};
776
+ async function promptAddonQuestions({ options: options$1, selectedAddonIds, workspace }) {
777
+ const selectedOfficialAddons = [];
778
+ selectedAddonIds.map((id) => {
779
+ if (officialAddons$1.find((a) => a.id === id)) selectedOfficialAddons.push(getAddonDetails(id));
780
+ });
781
+ const emptyAnswersReducer = (acc, id) => {
782
+ acc[id] = {};
783
+ return acc;
784
+ };
785
+ const answersOfficial = selectedOfficialAddons.map(({ id }) => id).reduce(emptyAnswersReducer, {});
783
786
  for (const addonOption of addonOptions) {
784
787
  const addonId = addonOption.id;
785
788
  const specifiedOptions = options$1.addons[addonId];
786
789
  if (!specifiedOptions) continue;
787
- const details$1 = getAddonDetails(addonId);
788
- if (!selectedAddons.find((d) => d.addon === details$1)) selectedAddons.push({
789
- type: "official",
790
- addon: details$1
791
- });
792
- official[addonId] ??= {};
793
- const optionEntries = Object.entries(details$1.options);
790
+ const details = getAddonDetails(addonId);
791
+ if (!selectedOfficialAddons.find((d) => d === details)) selectedOfficialAddons.push(details);
792
+ answersOfficial[addonId] ??= {};
793
+ const optionEntries = Object.entries(details.options);
794
794
  const specifiedOptionsObject = Object.fromEntries(specifiedOptions.map((option) => option.split(":", 2)));
795
795
  for (const option of specifiedOptions) {
796
- let [optionId, optionValue] = option.split(":", 2);
796
+ const [optionId, optionValue] = option.split(":", 2);
797
797
  const optionEntry = optionEntries.find(([id, question$1]) => {
798
798
  if (id === optionId) return true;
799
799
  if (question$1.group === optionId) {
800
- if (question$1.type === "select" || question$1.type === "multiselect") {
800
+ if (question$1.type === "select") {
801
801
  if (!question$1.options.some((opt) => opt.value === optionValue)) return false;
802
+ } else if (question$1.type === "multiselect") {
803
+ if (!(optionValue === "none" ? [] : optionValue.split(",")).every((val) => question$1.options.some((opt) => opt.value === val.trim()))) return false;
802
804
  }
803
805
  if (question$1.condition) return question$1.condition(specifiedOptionsObject);
804
806
  return true;
@@ -806,22 +808,31 @@ async function runAddCommand(options$1, selectedAddonIds) {
806
808
  return false;
807
809
  });
808
810
  if (!optionEntry) {
809
- const { choices } = getOptionChoices(details$1);
810
- throw new Error(`Invalid '${addonId}' option: '${option}'\nAvailable options: ${choices.join(", ")}`);
811
+ const { choices } = getOptionChoices(details);
812
+ errorAndExit(`Invalid '${addonId}' add-on option: '${option}'\nAvailable options: ${choices.join(", ")}`);
813
+ throw new Error();
811
814
  }
812
815
  const [questionId, question] = optionEntry;
813
- if (question.type === "multiselect" && optionValue === "none") optionValue = "";
814
- let existingOption = official[addonId][questionId];
816
+ if (question.type === "multiselect" && questionId === optionId) {
817
+ const invalidValues = (optionValue === "none" || optionValue === "" ? [] : optionValue.split(",")).filter((val) => !question.options.some((opt) => opt.value === val.trim()));
818
+ if (invalidValues.length > 0) {
819
+ const validValues = question.options.map((opt) => opt.value).join(", ");
820
+ errorAndExit(`Invalid '${addonId}' add-on option: '${option}'\nInvalid values: ${invalidValues.join(", ")}\nAvailable values: ${validValues}`);
821
+ }
822
+ }
823
+ let existingOption = answersOfficial[addonId][questionId];
815
824
  if (existingOption !== void 0) {
816
825
  if (typeof existingOption === "boolean") existingOption = existingOption ? "yes" : "no";
817
- throw new Error(`Conflicting '${addonId}' option: '${option}' conflicts with '${questionId}:${existingOption}'`);
826
+ errorAndExit(`Conflicting '${addonId}' option: '${option}' conflicts with '${questionId}:${existingOption}'`);
818
827
  }
819
- if (question.type === "boolean") official[addonId][questionId] = optionValue === "yes";
820
- else if (question.type === "number") official[addonId][questionId] = Number(optionValue);
821
- else official[addonId][questionId] = optionValue;
828
+ if (question.type === "boolean") answersOfficial[addonId][questionId] = optionValue === "yes";
829
+ else if (question.type === "number") answersOfficial[addonId][questionId] = Number(optionValue);
830
+ else if (question.type === "multiselect") if (optionValue === "none" || optionValue === "") answersOfficial[addonId][questionId] = [];
831
+ else answersOfficial[addonId][questionId] = optionValue.split(",").map((v) => v.trim());
832
+ else answersOfficial[addonId][questionId] = optionValue;
822
833
  }
823
- for (const [id, question] of Object.entries(details$1.options)) if (question.condition?.(official[addonId]) !== false) official[addonId][id] ??= question.default;
824
- else if (official[addonId][id] !== void 0) throw new Error(`Incompatible '${addonId}' option specified: '${official[addonId][id]}'`);
834
+ for (const [id, question] of Object.entries(details.options)) if (question.condition?.(answersOfficial[addonId]) !== false) answersOfficial[addonId][id] ??= question.default;
835
+ else if (answersOfficial[addonId][id] !== void 0) throw new Error(`Incompatible '${addonId}' option specified: '${answersOfficial[addonId][id]}'`);
825
836
  }
826
837
  if (options$1.community === true) {
827
838
  const promptOptions = (await Promise.all(communityAddonIds.map(async (id) => await getCommunityAddon(id)))).map((addon) => ({
@@ -843,51 +854,17 @@ async function runAddCommand(options$1, selectedAddonIds) {
843
854
  }
844
855
  options$1.community = selected;
845
856
  }
846
- if (Array.isArray(options$1.community) && options$1.community.length > 0) {
847
- const addons$1 = options$1.community.map((id) => {
848
- if (Object.values(Directive).some((directive) => id.startsWith(directive))) return id;
849
- if (!communityAddonIds.includes(id)) throw new Error(`Invalid community add-on specified: '${id}'\nAvailable options: ${communityAddonIds.join(", ")}`);
850
- return id;
851
- });
852
- const { start, stop } = J();
853
- try {
854
- start("Resolving community add-on packages");
855
- const pkgs = await Promise.all(addons$1.map(async (id) => {
856
- return await getPackageJSON({
857
- cwd: options$1.cwd,
858
- packageName: id
859
- });
860
- }));
861
- stop("Resolved community add-on packages");
862
- T.warn("The Svelte maintainers have not reviewed community add-ons for malicious code. Use at your discretion.");
863
- const paddingName = getPadding(pkgs.map(({ pkg }) => pkg.name));
864
- const paddingVersion = getPadding(pkgs.map(({ pkg }) => `(v${pkg.version})`));
865
- const packageInfos = pkgs.map(({ pkg, repo: _repo }) => {
866
- return `${import_picocolors$2.default.yellowBright(pkg.name.padEnd(paddingName))} ${import_picocolors$2.default.dim(`(v${pkg.version})`.padEnd(paddingVersion))} ${import_picocolors$2.default.dim(`(${_repo})`)}`;
867
- });
868
- T.message(packageInfos.join("\n"));
869
- if (await ke({ message: "Would you like to continue?" }) !== true) {
870
- De("Operation cancelled.");
871
- process.exit(1);
872
- }
873
- start("Downloading community add-on packages");
874
- const details$1 = await Promise.all(pkgs.map(async (opts) => downloadPackage(opts)));
875
- for (const addon of details$1) {
876
- const id = addon.id;
877
- community[id] ??= {};
878
- communityDetails.push(addon);
879
- selectedAddons.push({
880
- type: "community",
881
- addon
882
- });
883
- }
884
- stop("Downloaded community add-on packages");
885
- } catch (err) {
886
- stop("Failed to resolve community add-on packages", 1);
887
- throw err;
888
- }
889
- }
890
- let workspace = await createWorkspace({ cwd: options$1.cwd });
857
+ const selectedCommunityAddons = [];
858
+ const answersCommunity = selectedCommunityAddons.map(({ id }) => id).reduce(emptyAnswersReducer, {});
859
+ if (Array.isArray(options$1.community) && options$1.community.length > 0) selectedCommunityAddons.push(...await resolveCommunityAddons(options$1.cwd, options$1.community));
860
+ const selectedAddons = [...selectedOfficialAddons.map((addon) => ({
861
+ type: "official",
862
+ addon
863
+ })), ...selectedCommunityAddons.map((addon) => ({
864
+ type: "community",
865
+ addon
866
+ }))];
867
+ const addonSetupResults = setupAddons(selectedAddons.length ? selectedAddons.map(({ addon }) => addon) : officialAddons$1, workspace);
891
868
  if (selectedAddons.length === 0) {
892
869
  const allSetupResults = setupAddons(officialAddons$1, workspace);
893
870
  const addonOptions$1 = officialAddons$1.filter(({ id }) => allSetupResults[id].unsupported.length === 0).map(({ id, homepage, shortDescription }) => ({
@@ -913,10 +890,9 @@ async function runAddCommand(options$1, selectedAddonIds) {
913
890
  }
914
891
  }
915
892
  for (const { addon } of selectedAddons) {
916
- workspace = await createWorkspace(workspace);
917
- const missingDependencies = setupAddons(selectedAddons.map(({ addon: addon$1 }) => addon$1), workspace)[addon.id].dependsOn.filter((depId) => !selectedAddons.some((a) => a.addon.id === depId));
893
+ const missingDependencies = addonSetupResults[addon.id].dependsOn.filter((depId) => !selectedAddons.some((a) => a.addon.id === depId));
918
894
  for (const depId of missingDependencies) {
919
- const dependency = getAddonDetails(depId);
895
+ const dependency = officialAddons$1.find((a) => a.id === depId);
920
896
  if (!dependency) throw new Error(`'${addon.id}' depends on an invalid add-on: '${depId}'`);
921
897
  if (await ke({ message: `The ${import_picocolors$2.default.bold(import_picocolors$2.default.cyan(addon.id))} add-on requires ${import_picocolors$2.default.bold(import_picocolors$2.default.cyan(depId))} to also be setup. ${import_picocolors$2.default.green("Include it?")}` }) !== true) {
922
898
  De("Operation cancelled.");
@@ -929,7 +905,6 @@ async function runAddCommand(options$1, selectedAddonIds) {
929
905
  }
930
906
  }
931
907
  const addons = selectedAddons.map(({ addon }) => addon);
932
- const addonSetupResults = setupAddons(addons, workspace);
933
908
  const verifications = [...verifyCleanWorkingDirectory(options$1.cwd, options$1.gitCheck), ...verifyUnsupportedAddons(addons, addonSetupResults)];
934
909
  const fails = [];
935
910
  for (const verification of verifications) {
@@ -956,12 +931,12 @@ async function runAddCommand(options$1, selectedAddonIds) {
956
931
  const questionPrefix = selectedAddons.length > 1 ? `${addon.id}: ` : "";
957
932
  let values = {};
958
933
  if (type === "official") {
959
- official[addonId] ??= {};
960
- values = official[addonId];
934
+ answersOfficial[addonId] ??= {};
935
+ values = answersOfficial[addonId];
961
936
  }
962
937
  if (type === "community") {
963
- community[addonId] ??= {};
964
- values = community[addonId];
938
+ answersCommunity[addonId] ??= {};
939
+ values = answersCommunity[addonId];
965
940
  }
966
941
  for (const [questionId, question] of Object.entries(addon.options)) {
967
942
  if (question.condition?.(values) === false || values[questionId] !== void 0) continue;
@@ -998,19 +973,27 @@ async function runAddCommand(options$1, selectedAddonIds) {
998
973
  values[questionId] = answer;
999
974
  }
1000
975
  }
976
+ return {
977
+ selectedAddons,
978
+ answersOfficial,
979
+ answersCommunity
980
+ };
981
+ }
982
+ async function runAddonsApply({ answersOfficial, answersCommunity, options: options$1, selectedAddons, addonSetupResults, workspace, withLogArgs }) {
983
+ if (!addonSetupResults) addonSetupResults = setupAddons(selectedAddons.length ? selectedAddons.map(({ addon }) => addon) : officialAddons$1, workspace);
1001
984
  if (selectedAddons.length === 0) return {
1002
- packageManager: null,
1003
- nextSteps: []
985
+ nextSteps: [],
986
+ argsFormattedAddons: []
1004
987
  };
1005
- const officialDetails = Object.keys(official).map((id) => getAddonDetails(id));
1006
- const commDetails = Object.keys(community).map((id) => communityDetails.find((a) => a.id === id));
988
+ const officialDetails = Object.keys(answersOfficial).map((id) => getAddonDetails(id));
989
+ const commDetails = Object.keys(answersCommunity).map((id) => communityDetails.find((a) => a.id === id));
1007
990
  const details = officialDetails.concat(commDetails);
1008
991
  const addonMap = Object.assign({}, ...details.map((a) => ({ [a.id]: a })));
1009
992
  const { filesToFormat, pnpmBuildDependencies, status } = await applyAddons({
1010
993
  workspace,
1011
994
  addonSetupResults,
1012
995
  addons: addonMap,
1013
- options: official
996
+ options: answersOfficial
1014
997
  });
1015
998
  const addonSuccess = [];
1016
999
  for (const [addonId, info] of Object.entries(status)) if (info === "success") addonSuccess.push(addonId);
@@ -1021,18 +1004,42 @@ async function runAddCommand(options$1, selectedAddonIds) {
1021
1004
  if (addonSuccess.length === 0) {
1022
1005
  De("All selected add-ons were canceled.");
1023
1006
  process.exit(1);
1024
- } else if (addonSuccess.length === Object.entries(status).length) T.success("Successfully setup add-ons");
1025
- else T.success(`Successfully setup: ${addonSuccess.join(", ")}`);
1026
- let packageManager;
1027
- if (options$1.install) {
1028
- packageManager = options$1.install === true ? await packageManagerPrompt(options$1.cwd) : options$1.install;
1029
- if (packageManager) {
1030
- workspace.packageManager = packageManager;
1031
- await addPnpmBuildDependencies(workspace.cwd, packageManager, ["esbuild", ...pnpmBuildDependencies]);
1032
- await installDependencies(packageManager, options$1.cwd);
1007
+ } else {
1008
+ const highlighter$1 = getHighlighter();
1009
+ T.success(`Successfully setup add-ons: ${addonSuccess.map((c) => highlighter$1.addon(c)).join(", ")}`);
1010
+ }
1011
+ const packageManager = options$1.install === false ? null : options$1.install === true ? await packageManagerPrompt(options$1.cwd) : options$1.install;
1012
+ await addPnpmBuildDependencies(workspace.cwd, packageManager, ["esbuild", ...pnpmBuildDependencies]);
1013
+ const argsFormattedAddons = [];
1014
+ for (const { addon, type } of selectedAddons) {
1015
+ const addonId = addon.id;
1016
+ const answers = type === "official" ? answersOfficial[addonId] : answersCommunity[addonId];
1017
+ if (!answers) continue;
1018
+ const addonDetails = type === "official" ? getAddonDetails(addonId) : addon;
1019
+ const optionParts = [];
1020
+ for (const [optionId, value] of Object.entries(answers)) {
1021
+ if (value === void 0) continue;
1022
+ const question = addonDetails.options[optionId];
1023
+ if (!question) continue;
1024
+ let formattedValue;
1025
+ if (question.type === "boolean") formattedValue = value ? "yes" : "no";
1026
+ else if (question.type === "number") formattedValue = String(value);
1027
+ else if (question.type === "multiselect") if (Array.isArray(value)) if (value.length === 0) formattedValue = "none";
1028
+ else formattedValue = value.join(",");
1029
+ else formattedValue = String(value);
1030
+ else formattedValue = String(value);
1031
+ optionParts.push(`${optionId}:${formattedValue}`);
1033
1032
  }
1033
+ if (optionParts.length > 0) argsFormattedAddons.push(`${addonId}="${optionParts.join("+")}"`);
1034
+ else argsFormattedAddons.push(addonId);
1035
+ }
1036
+ if (packageManager === null || packageManager === void 0) argsFormattedAddons.push("--no-install");
1037
+ else argsFormattedAddons.push("--install", packageManager);
1038
+ if (withLogArgs) logArgs(packageManager ?? "npm", "add", argsFormattedAddons);
1039
+ if (packageManager) {
1040
+ workspace.packageManager = packageManager;
1041
+ await installDependencies(packageManager, options$1.cwd);
1034
1042
  }
1035
- workspace = await createWorkspace(workspace);
1036
1043
  if (filesToFormat.length > 0 && packageManager && !!workspace.dependencyVersion("prettier")) {
1037
1044
  const { start, stop } = J();
1038
1045
  start("Formatting modified files");
@@ -1052,10 +1059,10 @@ async function runAddCommand(options$1, selectedAddonIds) {
1052
1059
  return {
1053
1060
  nextSteps: selectedAddons.map(({ addon }) => {
1054
1061
  if (!addon.nextSteps) return;
1055
- const options$2 = official[addon.id];
1062
+ const addonOptions$1 = answersOfficial[addon.id];
1056
1063
  const addonNextSteps = addon.nextSteps({
1057
1064
  ...workspace,
1058
- options: options$2,
1065
+ options: addonOptions$1,
1059
1066
  highlighter
1060
1067
  });
1061
1068
  if (addonNextSteps.length === 0) return;
@@ -1063,10 +1070,40 @@ async function runAddCommand(options$1, selectedAddonIds) {
1063
1070
  addonMessage += ` - ${addonNextSteps.join("\n - ")}`;
1064
1071
  return addonMessage;
1065
1072
  }).filter((msg) => msg !== void 0),
1066
- packageManager
1073
+ argsFormattedAddons
1067
1074
  };
1068
1075
  }
1069
1076
  /**
1077
+ * Sanitizes the add-on arguments by checking for invalid add-ons and transforming aliases.
1078
+ * @param addonArgs The add-on arguments to sanitize.
1079
+ * @returns The sanitized add-on arguments.
1080
+ */
1081
+ function sanitizeAddons(addonArgs) {
1082
+ const officialAddonIds = officialAddons$1.map((addon) => addon.id);
1083
+ const invalidAddons = addonArgs.filter(({ id }) => !officialAddonIds.includes(id) && !aliases.includes(id)).map(({ id }) => id);
1084
+ if (invalidAddons.length > 0) errorAndExit(`Invalid add-ons specified: ${invalidAddons.join(", ")}`);
1085
+ return transformAliases(addonArgs);
1086
+ }
1087
+ /**
1088
+ * Handles passed add-on arguments, accumulating them into an array of {@link AddonArgs}.
1089
+ */
1090
+ function addonArgsHandler(acc, current) {
1091
+ const [addonId, optionFlags] = current.split("=", 2);
1092
+ if (acc.find(({ id }) => id === addonId)) errorAndExit(`Malformed arguments: Add-on '${addonId}' is repeated multiple times.`);
1093
+ try {
1094
+ const options$1 = parseAddonOptions(optionFlags);
1095
+ acc.push({
1096
+ id: addonId,
1097
+ options: options$1
1098
+ });
1099
+ } catch (error) {
1100
+ if (error instanceof Error) errorAndExit(error.message);
1101
+ console.error(error);
1102
+ process.exit(1);
1103
+ }
1104
+ return acc;
1105
+ }
1106
+ /**
1070
1107
  * Dedupes and transforms aliases into their respective addon id
1071
1108
  */
1072
1109
  function transformAliases(addons) {
@@ -1144,6 +1181,47 @@ function getOptionChoices(details) {
1144
1181
  groups
1145
1182
  };
1146
1183
  }
1184
+ async function resolveCommunityAddons(cwd, community) {
1185
+ const selectedAddons = [];
1186
+ const addons = community.map((id) => {
1187
+ if (Object.values(Directive).some((directive) => id.startsWith(directive))) return id;
1188
+ if (!communityAddonIds.includes(id)) throw new Error(`Invalid community add-on specified: '${id}'\nAvailable options: ${communityAddonIds.join(", ")}`);
1189
+ return id;
1190
+ });
1191
+ const { start, stop } = J();
1192
+ try {
1193
+ start("Resolving community add-on packages");
1194
+ const pkgs = await Promise.all(addons.map(async (id) => {
1195
+ return await getPackageJSON({
1196
+ cwd,
1197
+ packageName: id
1198
+ });
1199
+ }));
1200
+ stop("Resolved community add-on packages");
1201
+ T.warn("The Svelte maintainers have not reviewed community add-ons for malicious code. Use at your discretion.");
1202
+ const paddingName = getPadding(pkgs.map(({ pkg }) => pkg.name));
1203
+ const paddingVersion = getPadding(pkgs.map(({ pkg }) => `(v${pkg.version})`));
1204
+ const packageInfos = pkgs.map(({ pkg, repo: _repo }) => {
1205
+ return `${import_picocolors$2.default.yellowBright(pkg.name.padEnd(paddingName))} ${import_picocolors$2.default.dim(`(v${pkg.version})`.padEnd(paddingVersion))} ${import_picocolors$2.default.dim(`(${_repo})`)}`;
1206
+ });
1207
+ T.message(packageInfos.join("\n"));
1208
+ if (await ke({ message: "Would you like to continue?" }) !== true) {
1209
+ De("Operation cancelled.");
1210
+ process.exit(1);
1211
+ }
1212
+ start("Downloading community add-on packages");
1213
+ const details = await Promise.all(pkgs.map(async (opts) => downloadPackage(opts)));
1214
+ for (const addon of details) {
1215
+ communityDetails.push(addon);
1216
+ selectedAddons.push(addon);
1217
+ }
1218
+ stop("Downloaded community add-on packages");
1219
+ } catch (err) {
1220
+ stop("Failed to resolve community add-on packages", 1);
1221
+ throw err;
1222
+ }
1223
+ return selectedAddons;
1224
+ }
1147
1225
 
1148
1226
  //#endregion
1149
1227
  //#region ../create/playground.ts
@@ -1245,9 +1323,7 @@ function setupPlaygroundProject(url, playground, cwd, installDependencies$1, typ
1245
1323
  fs.writeFileSync(filePath$1, file.content, "utf8");
1246
1324
  }
1247
1325
  {
1248
- const shared = dist("shared.json");
1249
- const { files } = JSON.parse(fs.readFileSync(shared, "utf-8"));
1250
- const playgroundFiles = files.filter((file) => file.include.includes("playground"));
1326
+ const playgroundFiles = getSharedFiles().filter((file) => file.include.includes("playground"));
1251
1327
  for (const file of playgroundFiles) {
1252
1328
  let contentToWrite = file.contents;
1253
1329
  if (file.name === "src/lib/PlaygroundLayout.svelte") {
@@ -1320,15 +1396,20 @@ const langMap = {
1320
1396
  const templateChoices = templates.map((t) => t.name);
1321
1397
  const langOption = new Option("--types <lang>", "add type checking").choices(langs);
1322
1398
  const templateOption = new Option("--template <type>", "template to scaffold").choices(templateChoices);
1399
+ const noAddonsOption = new Option("--no-add-ons", "do not prompt to add add-ons").conflicts("add");
1400
+ const addOption = new Option("--add <addon...>", "add-on to include").default([]);
1323
1401
  const ProjectPathSchema = optional(string());
1324
1402
  const OptionsSchema = strictObject({
1325
1403
  types: pipe(optional(union([picklist(langs), boolean()])), transform((lang) => langMap[String(lang)])),
1326
1404
  addOns: boolean(),
1405
+ add: array(string()),
1327
1406
  install: union([boolean(), picklist(AGENT_NAMES)]),
1328
1407
  template: optional(picklist(templateChoices)),
1329
- fromPlayground: optional(string())
1408
+ fromPlayground: optional(string()),
1409
+ dirCheck: boolean()
1330
1410
  });
1331
- const create = new Command("create").description("scaffolds a new SvelteKit project").argument("[path]", "where the project will be created").addOption(templateOption).addOption(langOption).option("--no-types").option("--no-add-ons", "skips interactive add-on installer").option("--no-install", "skip installing dependencies").option("--from-playground <url>", "create a project from the svelte playground").addOption(installOption).configureHelp(helpConfig).action((projectPath, opts) => {
1411
+ const defaultPath = "./";
1412
+ const create = new Command("create").description("scaffolds a new SvelteKit project").argument("[path]", "where the project will be created").addOption(templateOption).addOption(langOption).option("--no-types").addOption(noAddonsOption).addOption(addOption).option("--no-install", "skip installing dependencies").option("--from-playground <url>", "create a project from the svelte playground").option("--no-dir-check", "even if the folder is not empty, no prompt will be shown").addOption(installOption).configureHelp(helpConfig).action((projectPath, opts) => {
1332
1413
  const cwd = parse(ProjectPathSchema, projectPath);
1333
1414
  const options$1 = parse(OptionsSchema, opts);
1334
1415
  if (options$1.fromPlayground && !validatePlaygroundUrl(options$1.fromPlayground)) {
@@ -1368,13 +1449,12 @@ const create = new Command("create").description("scaffolds a new SvelteKit proj
1368
1449
  steps.push("", `Stuck? Visit us at ${import_picocolors$1.default.cyan("https://svelte.dev/chat")}`);
1369
1450
  Ke(steps.join("\n"), "What's next?", { format: (line) => line });
1370
1451
  });
1371
- });
1452
+ }).showHelpAfterError(true);
1372
1453
  async function createProject(cwd, options$1) {
1373
1454
  if (options$1.fromPlayground) T.warn("The Svelte maintainers have not reviewed playgrounds for malicious code. Use at your discretion.");
1374
1455
  const { directory, template, language } = await We({
1375
1456
  directory: () => {
1376
1457
  if (cwd) return Promise.resolve(path.resolve(cwd));
1377
- const defaultPath = "./";
1378
1458
  return et({
1379
1459
  message: "Where would you like your project to be created?",
1380
1460
  placeholder: ` (hit Enter to use '${defaultPath}')`,
@@ -1382,15 +1462,16 @@ async function createProject(cwd, options$1) {
1382
1462
  });
1383
1463
  },
1384
1464
  force: async ({ results: { directory: directory$1 } }) => {
1385
- if (fs.existsSync(directory$1) && fs.readdirSync(directory$1).filter((x) => !x.startsWith(".git")).length > 0) {
1386
- const force = await ke({
1387
- message: "Directory not empty. Continue?",
1388
- initialValue: false
1389
- });
1390
- if (Vu(force) || !force) {
1391
- De("Exiting.");
1392
- process.exit(0);
1393
- }
1465
+ if (!options$1.dirCheck) return;
1466
+ if (!fs.existsSync(directory$1)) return;
1467
+ if (!fs.readdirSync(directory$1).some((file) => !file.startsWith(".git"))) return;
1468
+ const force = await ke({
1469
+ message: "Directory not empty. Continue?",
1470
+ initialValue: false
1471
+ });
1472
+ if (Vu(force) || !force) {
1473
+ De("Exiting.");
1474
+ process.exit(0);
1394
1475
  }
1395
1476
  },
1396
1477
  template: () => {
@@ -1432,32 +1513,76 @@ async function createProject(cwd, options$1) {
1432
1513
  process.exit(0);
1433
1514
  } });
1434
1515
  const projectPath = path.resolve(directory);
1516
+ const projectName = path.basename(projectPath);
1517
+ let selectedAddons = [];
1518
+ let answersOfficial = {};
1519
+ let answersCommunity = {};
1520
+ let sanitizedAddonsMap = {};
1521
+ const workspace = await createVirtualWorkspace({
1522
+ cwd: projectPath,
1523
+ template,
1524
+ packageManager: "npm",
1525
+ type: language
1526
+ });
1527
+ if (options$1.addOns || options$1.add.length > 0) {
1528
+ sanitizedAddonsMap = sanitizeAddons(options$1.add.reduce(addonArgsHandler, [])).reduce((acc, curr) => {
1529
+ acc[curr.id] = curr.options;
1530
+ return acc;
1531
+ }, {});
1532
+ const result = await promptAddonQuestions({
1533
+ options: {
1534
+ cwd: projectPath,
1535
+ install: false,
1536
+ gitCheck: false,
1537
+ community: [],
1538
+ addons: sanitizedAddonsMap
1539
+ },
1540
+ selectedAddonIds: Object.keys(sanitizedAddonsMap),
1541
+ workspace
1542
+ });
1543
+ selectedAddons = result.selectedAddons;
1544
+ answersOfficial = result.answersOfficial;
1545
+ answersCommunity = result.answersCommunity;
1546
+ }
1435
1547
  create$1(projectPath, {
1436
- name: path.basename(projectPath),
1548
+ name: projectName,
1437
1549
  template,
1438
1550
  types: language
1439
1551
  });
1440
1552
  if (options$1.fromPlayground) await createProjectFromPlayground(options$1.fromPlayground, projectPath, language === "typescript");
1441
1553
  T.success("Project created");
1442
- let packageManager;
1443
1554
  let addOnNextSteps = [];
1444
- const installDeps = async (install) => {
1445
- packageManager = install === true ? await packageManagerPrompt(projectPath) : install;
1446
- await addPnpmBuildDependencies(projectPath, packageManager, ["esbuild"]);
1447
- if (packageManager) await installDependencies(packageManager, projectPath);
1448
- };
1449
- if (options$1.addOns) {
1450
- const { nextSteps, packageManager: pm } = await runAddCommand({
1451
- cwd: projectPath,
1452
- install: options$1.install,
1453
- gitCheck: false,
1454
- community: [],
1455
- addons: {}
1456
- }, []);
1457
- packageManager = pm;
1555
+ let argsFormattedAddons = [];
1556
+ if (options$1.addOns || options$1.add.length > 0) {
1557
+ const { nextSteps, argsFormattedAddons: argsFormatted$1 } = await runAddonsApply({
1558
+ answersOfficial,
1559
+ answersCommunity,
1560
+ options: {
1561
+ cwd: projectPath,
1562
+ install: false,
1563
+ gitCheck: false,
1564
+ community: [],
1565
+ addons: sanitizedAddonsMap
1566
+ },
1567
+ selectedAddons,
1568
+ addonSetupResults: void 0,
1569
+ workspace
1570
+ });
1571
+ argsFormattedAddons = argsFormatted$1;
1458
1572
  addOnNextSteps = nextSteps;
1459
- } else if (options$1.install) await installDeps(options$1.install);
1460
- if (packageManager === null && options$1.install) await installDeps(options$1.install);
1573
+ }
1574
+ const packageManager = options$1.install === false ? null : options$1.install === true ? await packageManagerPrompt(projectPath) : options$1.install;
1575
+ const argsFormatted = [cwd ?? defaultPath];
1576
+ argsFormatted.push("--template", template);
1577
+ if (language === "typescript") argsFormatted.push("--types", "ts");
1578
+ else if (language === "checkjs") argsFormatted.push("--types", "jsdoc");
1579
+ else if (language === "none") argsFormatted.push("--no-types");
1580
+ if (argsFormattedAddons.length > 0) argsFormatted.push("--add", ...argsFormattedAddons);
1581
+ if (packageManager === null || packageManager === void 0) argsFormatted.push("--no-install");
1582
+ else argsFormatted.push("--install", packageManager);
1583
+ logArgs(packageManager ?? "npm", "create", argsFormatted);
1584
+ await addPnpmBuildDependencies(projectPath, packageManager, ["esbuild"]);
1585
+ if (packageManager) await installDependencies(packageManager, projectPath);
1461
1586
  return {
1462
1587
  directory: projectPath,
1463
1588
  addOnNextSteps,
@@ -1483,6 +1608,28 @@ async function confirmExternalDependencies(dependencies) {
1483
1608
  }
1484
1609
  return installDeps;
1485
1610
  }
1611
+ async function createVirtualWorkspace({ cwd, template, packageManager, type }) {
1612
+ const tentativeWorkspace = await createWorkspace({
1613
+ cwd,
1614
+ packageManager
1615
+ });
1616
+ const virtualWorkspace = {
1617
+ ...tentativeWorkspace,
1618
+ typescript: type === "typescript",
1619
+ files: {
1620
+ ...tentativeWorkspace.files,
1621
+ viteConfig: type === "typescript" ? commonFilePaths.viteConfigTS : commonFilePaths.viteConfig,
1622
+ svelteConfig: type === "typescript" ? commonFilePaths.svelteConfigTS : commonFilePaths.svelteConfig
1623
+ },
1624
+ kit: void 0,
1625
+ dependencyVersion: () => void 0
1626
+ };
1627
+ if (template === "minimal" || template === "demo" || template === "library") virtualWorkspace.kit = {
1628
+ routesDirectory: "src/routes",
1629
+ libDirectory: "src/lib"
1630
+ };
1631
+ return virtualWorkspace;
1632
+ }
1486
1633
 
1487
1634
  //#endregion
1488
1635
  //#region commands/migrate.ts