sv 0.9.15 → 0.10.1

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,18 +1,28 @@
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 getUserAgent, c as packageManagerPrompt, et as __toESM, f as getSharedFiles, g as parseJson, i as addPnpmBuildDependencies, l as detect, n as templates, o as installDependencies, p as resolveCommand, q as from, r as AGENT_NAMES, s as installOption, t as create$1, v as parseSvelte, z as Ue } from "./create-BOEKhcVU.js";
3
- import { a as communityAddonIds, c as officialAddons, d as createDefault, f as addDefault, i as createWorkspace, l as formatFiles, m as overrideProperties, o as getCommunityAddon, p as create$2, r as setupAddons, s as getAddonDetails, t as applyAddons, u as getHighlighter } from "./install-BoYywMIH.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.15";
15
+ var version = "0.10.1";
16
+
17
+ //#endregion
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
+ }
16
26
 
17
27
  //#endregion
18
28
  //#region ../../node_modules/.pnpm/valibot@0.41.0_typescript@5.9.3/node_modules/valibot/dist/index.js
@@ -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,68 @@ function parseAddonOptions(optionFlags) {
532
532
  }
533
533
  return options$1;
534
534
  }
535
+ function logArgs(agent, command, args, lastArgs = []) {
536
+ const allArgs = [
537
+ "sv",
538
+ command,
539
+ ...args
540
+ ];
541
+ if (agent === null || agent === void 0) allArgs.push("--no-install");
542
+ else allArgs.push("--install", agent);
543
+ const res = resolveCommand(agent ?? "npm", "execute", [...allArgs, ...lastArgs]);
544
+ T.message(import_picocolors$3.default.dim([res.command, ...res.args].join(" ")));
545
+ }
546
+ function errorAndExit(message) {
547
+ T.error(message);
548
+ De("Operation failed.");
549
+ process.exit(1);
550
+ }
551
+
552
+ //#endregion
553
+ //#region commands/add/verifiers.ts
554
+ function verifyCleanWorkingDirectory(cwd, gitCheck) {
555
+ const verifications = [];
556
+ if (gitCheck) verifications.push({
557
+ name: "clean working directory",
558
+ run: async () => {
559
+ try {
560
+ const { stdout: stdout$1 } = await promisify(exec)("git status --short", { cwd });
561
+ if (stdout$1) return {
562
+ success: false,
563
+ message: "Found modified files"
564
+ };
565
+ return {
566
+ success: true,
567
+ message: void 0
568
+ };
569
+ } catch {
570
+ return {
571
+ success: true,
572
+ message: "Not a git repository"
573
+ };
574
+ }
575
+ }
576
+ });
577
+ return verifications;
578
+ }
579
+ function verifyUnsupportedAddons(addons, addonSetupResult) {
580
+ const verifications = [];
581
+ verifications.push({
582
+ name: "unsupported add-ons",
583
+ run: () => {
584
+ const reasons = addons.flatMap((a) => addonSetupResult[a.id].unsupported.map((reason) => ({
585
+ id: a.id,
586
+ reason
587
+ })));
588
+ if (reasons.length === 0) return {
589
+ success: true,
590
+ message: void 0
591
+ };
592
+ throw new UnsupportedError(reasons);
593
+ }
594
+ });
595
+ return verifications;
596
+ }
535
597
 
536
598
  //#endregion
537
599
  //#region commands/add/fetch-packages.ts
@@ -612,52 +674,6 @@ async function fetchPackageJSON(packageName) {
612
674
  return await resp.json();
613
675
  }
614
676
 
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
677
  //#endregion
662
678
  //#region commands/add/index.ts
663
679
  var import_picocolors$2 = /* @__PURE__ */ __toESM(require_picocolors(), 1);
@@ -675,24 +691,7 @@ const OptionsSchema$1 = strictObject({
675
691
  });
676
692
  const defaultPkgPath = up();
677
693
  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({
694
+ 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
695
  ...helpConfig,
697
696
  formatHelp(cmd, helper) {
698
697
  const termWidth = helper.padWidth(cmd, helper);
@@ -748,57 +747,61 @@ const add = new Command("add").description("applies specified add-ons into a pro
748
747
  ]);
749
748
  return output.join("\n");
750
749
  }
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
- }
750
+ }).action(async (addonArgs, opts) => {
751
+ 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>`");
752
+ else if (!fs.existsSync(path.resolve(opts.cwd, "package.json"))) errorAndExit(`Invalid workspace: Path '${path.resolve(opts.cwd)}' is not a valid workspace.`);
753
+ const selectedAddonArgs = sanitizeAddons(addonArgs);
765
754
  const options$1 = parse(OptionsSchema$1, {
766
755
  ...opts,
767
756
  addons: {}
768
757
  });
769
- const selectedAddons = transformAliases(addonArgs);
770
- selectedAddons.forEach((addon) => options$1.addons[addon.id] = addon.options);
758
+ selectedAddonArgs.forEach((addon) => options$1.addons[addon.id] = addon.options);
759
+ const workspace = await createWorkspace({ cwd: options$1.cwd });
771
760
  runCommand(async () => {
772
- const { nextSteps } = await runAddCommand(options$1, selectedAddons.map(({ id }) => id));
761
+ const { answersCommunity, answersOfficial, selectedAddons } = await promptAddonQuestions({
762
+ options: options$1,
763
+ selectedAddonIds: selectedAddonArgs.map(({ id }) => id),
764
+ workspace
765
+ });
766
+ const { nextSteps } = await runAddonsApply({
767
+ answersOfficial,
768
+ answersCommunity,
769
+ options: options$1,
770
+ selectedAddons,
771
+ workspace,
772
+ fromCommand: "add"
773
+ });
773
774
  if (nextSteps.length > 0) Ke(nextSteps.join("\n"), "Next steps", { format: (line) => line });
774
775
  });
775
776
  });
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 = {};
777
+ async function promptAddonQuestions({ options: options$1, selectedAddonIds, workspace }) {
778
+ const selectedOfficialAddons = [];
779
+ selectedAddonIds.map((id) => {
780
+ if (officialAddons$1.find((a) => a.id === id)) selectedOfficialAddons.push(getAddonDetails(id));
781
+ });
782
+ const emptyAnswersReducer = (acc, id) => {
783
+ acc[id] = {};
784
+ return acc;
785
+ };
786
+ const answersOfficial = selectedOfficialAddons.map(({ id }) => id).reduce(emptyAnswersReducer, {});
783
787
  for (const addonOption of addonOptions) {
784
788
  const addonId = addonOption.id;
785
789
  const specifiedOptions = options$1.addons[addonId];
786
790
  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);
791
+ const details = getAddonDetails(addonId);
792
+ if (!selectedOfficialAddons.find((d) => d === details)) selectedOfficialAddons.push(details);
793
+ answersOfficial[addonId] ??= {};
794
+ const optionEntries = Object.entries(details.options);
794
795
  const specifiedOptionsObject = Object.fromEntries(specifiedOptions.map((option) => option.split(":", 2)));
795
796
  for (const option of specifiedOptions) {
796
- let [optionId, optionValue] = option.split(":", 2);
797
+ const [optionId, optionValue] = option.split(":", 2);
797
798
  const optionEntry = optionEntries.find(([id, question$1]) => {
798
799
  if (id === optionId) return true;
799
800
  if (question$1.group === optionId) {
800
- if (question$1.type === "select" || question$1.type === "multiselect") {
801
+ if (question$1.type === "select") {
801
802
  if (!question$1.options.some((opt) => opt.value === optionValue)) return false;
803
+ } else if (question$1.type === "multiselect") {
804
+ if (!(optionValue === "none" ? [] : optionValue.split(",")).every((val) => question$1.options.some((opt) => opt.value === val.trim()))) return false;
802
805
  }
803
806
  if (question$1.condition) return question$1.condition(specifiedOptionsObject);
804
807
  return true;
@@ -806,22 +809,31 @@ async function runAddCommand(options$1, selectedAddonIds) {
806
809
  return false;
807
810
  });
808
811
  if (!optionEntry) {
809
- const { choices } = getOptionChoices(details$1);
810
- throw new Error(`Invalid '${addonId}' option: '${option}'\nAvailable options: ${choices.join(", ")}`);
812
+ const { choices } = getOptionChoices(details);
813
+ errorAndExit(`Invalid '${addonId}' add-on option: '${option}'\nAvailable options: ${choices.join(", ")}`);
814
+ throw new Error();
811
815
  }
812
816
  const [questionId, question] = optionEntry;
813
- if (question.type === "multiselect" && optionValue === "none") optionValue = "";
814
- let existingOption = official[addonId][questionId];
817
+ if (question.type === "multiselect" && questionId === optionId) {
818
+ const invalidValues = (optionValue === "none" || optionValue === "" ? [] : optionValue.split(",")).filter((val) => !question.options.some((opt) => opt.value === val.trim()));
819
+ if (invalidValues.length > 0) {
820
+ const validValues = question.options.map((opt) => opt.value).join(", ");
821
+ errorAndExit(`Invalid '${addonId}' add-on option: '${option}'\nInvalid values: ${invalidValues.join(", ")}\nAvailable values: ${validValues}`);
822
+ }
823
+ }
824
+ let existingOption = answersOfficial[addonId][questionId];
815
825
  if (existingOption !== void 0) {
816
826
  if (typeof existingOption === "boolean") existingOption = existingOption ? "yes" : "no";
817
- throw new Error(`Conflicting '${addonId}' option: '${option}' conflicts with '${questionId}:${existingOption}'`);
827
+ errorAndExit(`Conflicting '${addonId}' option: '${option}' conflicts with '${questionId}:${existingOption}'`);
818
828
  }
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;
829
+ if (question.type === "boolean") answersOfficial[addonId][questionId] = optionValue === "yes";
830
+ else if (question.type === "number") answersOfficial[addonId][questionId] = Number(optionValue);
831
+ else if (question.type === "multiselect") if (optionValue === "none" || optionValue === "") answersOfficial[addonId][questionId] = [];
832
+ else answersOfficial[addonId][questionId] = optionValue.split(",").map((v) => v.trim());
833
+ else answersOfficial[addonId][questionId] = optionValue;
822
834
  }
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]}'`);
835
+ for (const [id, question] of Object.entries(details.options)) if (question.condition?.(answersOfficial[addonId]) !== false) answersOfficial[addonId][id] ??= question.default;
836
+ else if (answersOfficial[addonId][id] !== void 0) throw new Error(`Incompatible '${addonId}' option specified: '${answersOfficial[addonId][id]}'`);
825
837
  }
826
838
  if (options$1.community === true) {
827
839
  const promptOptions = (await Promise.all(communityAddonIds.map(async (id) => await getCommunityAddon(id)))).map((addon) => ({
@@ -843,51 +855,17 @@ async function runAddCommand(options$1, selectedAddonIds) {
843
855
  }
844
856
  options$1.community = selected;
845
857
  }
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 });
858
+ const selectedCommunityAddons = [];
859
+ const answersCommunity = selectedCommunityAddons.map(({ id }) => id).reduce(emptyAnswersReducer, {});
860
+ if (Array.isArray(options$1.community) && options$1.community.length > 0) selectedCommunityAddons.push(...await resolveCommunityAddons(options$1.cwd, options$1.community));
861
+ const selectedAddons = [...selectedOfficialAddons.map((addon) => ({
862
+ type: "official",
863
+ addon
864
+ })), ...selectedCommunityAddons.map((addon) => ({
865
+ type: "community",
866
+ addon
867
+ }))];
868
+ const addonSetupResults = setupAddons(selectedAddons.length ? selectedAddons.map(({ addon }) => addon) : officialAddons$1, workspace);
891
869
  if (selectedAddons.length === 0) {
892
870
  const allSetupResults = setupAddons(officialAddons$1, workspace);
893
871
  const addonOptions$1 = officialAddons$1.filter(({ id }) => allSetupResults[id].unsupported.length === 0).map(({ id, homepage, shortDescription }) => ({
@@ -913,10 +891,9 @@ async function runAddCommand(options$1, selectedAddonIds) {
913
891
  }
914
892
  }
915
893
  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));
894
+ const missingDependencies = addonSetupResults[addon.id].dependsOn.filter((depId) => !selectedAddons.some((a) => a.addon.id === depId));
918
895
  for (const depId of missingDependencies) {
919
- const dependency = getAddonDetails(depId);
896
+ const dependency = officialAddons$1.find((a) => a.id === depId);
920
897
  if (!dependency) throw new Error(`'${addon.id}' depends on an invalid add-on: '${depId}'`);
921
898
  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
899
  De("Operation cancelled.");
@@ -929,7 +906,6 @@ async function runAddCommand(options$1, selectedAddonIds) {
929
906
  }
930
907
  }
931
908
  const addons = selectedAddons.map(({ addon }) => addon);
932
- const addonSetupResults = setupAddons(addons, workspace);
933
909
  const verifications = [...verifyCleanWorkingDirectory(options$1.cwd, options$1.gitCheck), ...verifyUnsupportedAddons(addons, addonSetupResults)];
934
910
  const fails = [];
935
911
  for (const verification of verifications) {
@@ -956,12 +932,12 @@ async function runAddCommand(options$1, selectedAddonIds) {
956
932
  const questionPrefix = selectedAddons.length > 1 ? `${addon.id}: ` : "";
957
933
  let values = {};
958
934
  if (type === "official") {
959
- official[addonId] ??= {};
960
- values = official[addonId];
935
+ answersOfficial[addonId] ??= {};
936
+ values = answersOfficial[addonId];
961
937
  }
962
938
  if (type === "community") {
963
- community[addonId] ??= {};
964
- values = community[addonId];
939
+ answersCommunity[addonId] ??= {};
940
+ values = answersCommunity[addonId];
965
941
  }
966
942
  for (const [questionId, question] of Object.entries(addon.options)) {
967
943
  if (question.condition?.(values) === false || values[questionId] !== void 0) continue;
@@ -998,19 +974,27 @@ async function runAddCommand(options$1, selectedAddonIds) {
998
974
  values[questionId] = answer;
999
975
  }
1000
976
  }
977
+ return {
978
+ selectedAddons,
979
+ answersOfficial,
980
+ answersCommunity
981
+ };
982
+ }
983
+ async function runAddonsApply({ answersOfficial, answersCommunity, options: options$1, selectedAddons, addonSetupResults, workspace, fromCommand }) {
984
+ if (!addonSetupResults) addonSetupResults = setupAddons(selectedAddons.length ? selectedAddons.map(({ addon }) => addon) : officialAddons$1, workspace);
1001
985
  if (selectedAddons.length === 0) return {
1002
- packageManager: null,
1003
- nextSteps: []
986
+ nextSteps: [],
987
+ argsFormattedAddons: []
1004
988
  };
1005
- const officialDetails = Object.keys(official).map((id) => getAddonDetails(id));
1006
- const commDetails = Object.keys(community).map((id) => communityDetails.find((a) => a.id === id));
989
+ const officialDetails = Object.keys(answersOfficial).map((id) => getAddonDetails(id));
990
+ const commDetails = Object.keys(answersCommunity).map((id) => communityDetails.find((a) => a.id === id));
1007
991
  const details = officialDetails.concat(commDetails);
1008
992
  const addonMap = Object.assign({}, ...details.map((a) => ({ [a.id]: a })));
1009
993
  const { filesToFormat, pnpmBuildDependencies, status } = await applyAddons({
1010
994
  workspace,
1011
995
  addonSetupResults,
1012
996
  addons: addonMap,
1013
- options: official
997
+ options: answersOfficial
1014
998
  });
1015
999
  const addonSuccess = [];
1016
1000
  for (const [addonId, info] of Object.entries(status)) if (info === "success") addonSuccess.push(addonId);
@@ -1021,18 +1005,40 @@ async function runAddCommand(options$1, selectedAddonIds) {
1021
1005
  if (addonSuccess.length === 0) {
1022
1006
  De("All selected add-ons were canceled.");
1023
1007
  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);
1008
+ } else {
1009
+ const highlighter$1 = getHighlighter();
1010
+ T.success(`Successfully setup add-ons: ${addonSuccess.map((c) => highlighter$1.addon(c)).join(", ")}`);
1011
+ }
1012
+ const packageManager = options$1.install === false ? null : options$1.install === true ? await packageManagerPrompt(options$1.cwd) : options$1.install;
1013
+ await addPnpmBuildDependencies(workspace.cwd, packageManager, ["esbuild", ...pnpmBuildDependencies]);
1014
+ const argsFormattedAddons = [];
1015
+ for (const { addon, type } of selectedAddons) {
1016
+ const addonId = addon.id;
1017
+ const answers = type === "official" ? answersOfficial[addonId] : answersCommunity[addonId];
1018
+ if (!answers) continue;
1019
+ const addonDetails = type === "official" ? getAddonDetails(addonId) : addon;
1020
+ const optionParts = [];
1021
+ for (const [optionId, value] of Object.entries(answers)) {
1022
+ if (value === void 0) continue;
1023
+ const question = addonDetails.options[optionId];
1024
+ if (!question) continue;
1025
+ let formattedValue;
1026
+ if (question.type === "boolean") formattedValue = value ? "yes" : "no";
1027
+ else if (question.type === "number") formattedValue = String(value);
1028
+ else if (question.type === "multiselect") if (Array.isArray(value)) if (value.length === 0) formattedValue = "none";
1029
+ else formattedValue = value.join(",");
1030
+ else formattedValue = String(value);
1031
+ else formattedValue = String(value);
1032
+ optionParts.push(`${optionId}:${formattedValue}`);
1033
1033
  }
1034
+ if (optionParts.length > 0) argsFormattedAddons.push(`${addonId}="${optionParts.join("+")}"`);
1035
+ else argsFormattedAddons.push(addonId);
1036
+ }
1037
+ if (fromCommand === "add") logArgs(packageManager, "add", argsFormattedAddons);
1038
+ if (packageManager) {
1039
+ workspace.packageManager = packageManager;
1040
+ await installDependencies(packageManager, options$1.cwd);
1034
1041
  }
1035
- workspace = await createWorkspace(workspace);
1036
1042
  if (filesToFormat.length > 0 && packageManager && !!workspace.dependencyVersion("prettier")) {
1037
1043
  const { start, stop } = J();
1038
1044
  start("Formatting modified files");
@@ -1052,10 +1058,10 @@ async function runAddCommand(options$1, selectedAddonIds) {
1052
1058
  return {
1053
1059
  nextSteps: selectedAddons.map(({ addon }) => {
1054
1060
  if (!addon.nextSteps) return;
1055
- const options$2 = official[addon.id];
1061
+ const addonOptions$1 = answersOfficial[addon.id];
1056
1062
  const addonNextSteps = addon.nextSteps({
1057
1063
  ...workspace,
1058
- options: options$2,
1064
+ options: addonOptions$1,
1059
1065
  highlighter
1060
1066
  });
1061
1067
  if (addonNextSteps.length === 0) return;
@@ -1063,10 +1069,40 @@ async function runAddCommand(options$1, selectedAddonIds) {
1063
1069
  addonMessage += ` - ${addonNextSteps.join("\n - ")}`;
1064
1070
  return addonMessage;
1065
1071
  }).filter((msg) => msg !== void 0),
1066
- packageManager
1072
+ argsFormattedAddons
1067
1073
  };
1068
1074
  }
1069
1075
  /**
1076
+ * Sanitizes the add-on arguments by checking for invalid add-ons and transforming aliases.
1077
+ * @param addonArgs The add-on arguments to sanitize.
1078
+ * @returns The sanitized add-on arguments.
1079
+ */
1080
+ function sanitizeAddons(addonArgs) {
1081
+ const officialAddonIds = officialAddons$1.map((addon) => addon.id);
1082
+ const invalidAddons = addonArgs.filter(({ id }) => !officialAddonIds.includes(id) && !aliases.includes(id)).map(({ id }) => id);
1083
+ if (invalidAddons.length > 0) errorAndExit(`Invalid add-ons specified: ${invalidAddons.join(", ")}`);
1084
+ return transformAliases(addonArgs);
1085
+ }
1086
+ /**
1087
+ * Handles passed add-on arguments, accumulating them into an array of {@link AddonArgs}.
1088
+ */
1089
+ function addonArgsHandler(acc, current) {
1090
+ const [addonId, optionFlags] = current.split("=", 2);
1091
+ if (acc.find(({ id }) => id === addonId)) errorAndExit(`Malformed arguments: Add-on '${addonId}' is repeated multiple times.`);
1092
+ try {
1093
+ const options$1 = parseAddonOptions(optionFlags);
1094
+ acc.push({
1095
+ id: addonId,
1096
+ options: options$1
1097
+ });
1098
+ } catch (error) {
1099
+ if (error instanceof Error) errorAndExit(error.message);
1100
+ console.error(error);
1101
+ process.exit(1);
1102
+ }
1103
+ return acc;
1104
+ }
1105
+ /**
1070
1106
  * Dedupes and transforms aliases into their respective addon id
1071
1107
  */
1072
1108
  function transformAliases(addons) {
@@ -1144,6 +1180,47 @@ function getOptionChoices(details) {
1144
1180
  groups
1145
1181
  };
1146
1182
  }
1183
+ async function resolveCommunityAddons(cwd, community) {
1184
+ const selectedAddons = [];
1185
+ const addons = community.map((id) => {
1186
+ if (Object.values(Directive).some((directive) => id.startsWith(directive))) return id;
1187
+ if (!communityAddonIds.includes(id)) throw new Error(`Invalid community add-on specified: '${id}'\nAvailable options: ${communityAddonIds.join(", ")}`);
1188
+ return id;
1189
+ });
1190
+ const { start, stop } = J();
1191
+ try {
1192
+ start("Resolving community add-on packages");
1193
+ const pkgs = await Promise.all(addons.map(async (id) => {
1194
+ return await getPackageJSON({
1195
+ cwd,
1196
+ packageName: id
1197
+ });
1198
+ }));
1199
+ stop("Resolved community add-on packages");
1200
+ T.warn("The Svelte maintainers have not reviewed community add-ons for malicious code. Use at your discretion.");
1201
+ const paddingName = getPadding(pkgs.map(({ pkg }) => pkg.name));
1202
+ const paddingVersion = getPadding(pkgs.map(({ pkg }) => `(v${pkg.version})`));
1203
+ const packageInfos = pkgs.map(({ pkg, repo: _repo }) => {
1204
+ 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})`)}`;
1205
+ });
1206
+ T.message(packageInfos.join("\n"));
1207
+ if (await ke({ message: "Would you like to continue?" }) !== true) {
1208
+ De("Operation cancelled.");
1209
+ process.exit(1);
1210
+ }
1211
+ start("Downloading community add-on packages");
1212
+ const details = await Promise.all(pkgs.map(async (opts) => downloadPackage(opts)));
1213
+ for (const addon of details) {
1214
+ communityDetails.push(addon);
1215
+ selectedAddons.push(addon);
1216
+ }
1217
+ stop("Downloaded community add-on packages");
1218
+ } catch (err) {
1219
+ stop("Failed to resolve community add-on packages", 1);
1220
+ throw err;
1221
+ }
1222
+ return selectedAddons;
1223
+ }
1147
1224
 
1148
1225
  //#endregion
1149
1226
  //#region ../create/playground.ts
@@ -1318,16 +1395,20 @@ const langMap = {
1318
1395
  const templateChoices = templates.map((t) => t.name);
1319
1396
  const langOption = new Option("--types <lang>", "add type checking").choices(langs);
1320
1397
  const templateOption = new Option("--template <type>", "template to scaffold").choices(templateChoices);
1398
+ const noAddonsOption = new Option("--no-add-ons", "do not prompt to add add-ons").conflicts("add");
1399
+ const addOption = new Option("--add <addon...>", "add-on to include").default([]);
1321
1400
  const ProjectPathSchema = optional(string());
1322
1401
  const OptionsSchema = strictObject({
1323
1402
  types: pipe(optional(union([picklist(langs), boolean()])), transform((lang) => langMap[String(lang)])),
1324
1403
  addOns: boolean(),
1404
+ add: array(string()),
1325
1405
  install: union([boolean(), picklist(AGENT_NAMES)]),
1326
1406
  template: optional(picklist(templateChoices)),
1327
1407
  fromPlayground: optional(string()),
1328
1408
  dirCheck: boolean()
1329
1409
  });
1330
- 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").option("--no-dir-check", "even if the folder is not empty, no prompt will be shown").addOption(installOption).configureHelp(helpConfig).action((projectPath, opts) => {
1410
+ const defaultPath = "./";
1411
+ 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) => {
1331
1412
  const cwd = parse(ProjectPathSchema, projectPath);
1332
1413
  const options$1 = parse(OptionsSchema, opts);
1333
1414
  if (options$1.fromPlayground && !validatePlaygroundUrl(options$1.fromPlayground)) {
@@ -1367,13 +1448,12 @@ const create = new Command("create").description("scaffolds a new SvelteKit proj
1367
1448
  steps.push("", `Stuck? Visit us at ${import_picocolors$1.default.cyan("https://svelte.dev/chat")}`);
1368
1449
  Ke(steps.join("\n"), "What's next?", { format: (line) => line });
1369
1450
  });
1370
- });
1451
+ }).showHelpAfterError(true);
1371
1452
  async function createProject(cwd, options$1) {
1372
1453
  if (options$1.fromPlayground) T.warn("The Svelte maintainers have not reviewed playgrounds for malicious code. Use at your discretion.");
1373
1454
  const { directory, template, language } = await We({
1374
1455
  directory: () => {
1375
1456
  if (cwd) return Promise.resolve(path.resolve(cwd));
1376
- const defaultPath = "./";
1377
1457
  return et({
1378
1458
  message: "Where would you like your project to be created?",
1379
1459
  placeholder: ` (hit Enter to use '${defaultPath}')`,
@@ -1432,32 +1512,75 @@ async function createProject(cwd, options$1) {
1432
1512
  process.exit(0);
1433
1513
  } });
1434
1514
  const projectPath = path.resolve(directory);
1515
+ const projectName = path.basename(projectPath);
1516
+ let selectedAddons = [];
1517
+ let answersOfficial = {};
1518
+ let answersCommunity = {};
1519
+ let sanitizedAddonsMap = {};
1520
+ const workspace = await createVirtualWorkspace({
1521
+ cwd: projectPath,
1522
+ template,
1523
+ packageManager: "npm",
1524
+ type: language
1525
+ });
1526
+ if (options$1.addOns || options$1.add.length > 0) {
1527
+ sanitizedAddonsMap = sanitizeAddons(options$1.add.reduce(addonArgsHandler, [])).reduce((acc, curr) => {
1528
+ acc[curr.id] = curr.options;
1529
+ return acc;
1530
+ }, {});
1531
+ const result = await promptAddonQuestions({
1532
+ options: {
1533
+ cwd: projectPath,
1534
+ install: false,
1535
+ gitCheck: false,
1536
+ community: [],
1537
+ addons: sanitizedAddonsMap
1538
+ },
1539
+ selectedAddonIds: Object.keys(sanitizedAddonsMap),
1540
+ workspace
1541
+ });
1542
+ selectedAddons = result.selectedAddons;
1543
+ answersOfficial = result.answersOfficial;
1544
+ answersCommunity = result.answersCommunity;
1545
+ }
1435
1546
  create$1(projectPath, {
1436
- name: path.basename(projectPath),
1547
+ name: projectName,
1437
1548
  template,
1438
1549
  types: language
1439
1550
  });
1440
1551
  if (options$1.fromPlayground) await createProjectFromPlayground(options$1.fromPlayground, projectPath, language === "typescript");
1441
1552
  T.success("Project created");
1442
- let packageManager;
1443
1553
  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;
1554
+ let argsFormattedAddons = [];
1555
+ if (options$1.addOns || options$1.add.length > 0) {
1556
+ const { nextSteps, argsFormattedAddons: argsFormatted$1 } = await runAddonsApply({
1557
+ answersOfficial,
1558
+ answersCommunity,
1559
+ options: {
1560
+ cwd: projectPath,
1561
+ install: false,
1562
+ gitCheck: false,
1563
+ community: [],
1564
+ addons: sanitizedAddonsMap
1565
+ },
1566
+ selectedAddons,
1567
+ addonSetupResults: void 0,
1568
+ workspace,
1569
+ fromCommand: "create"
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 = [];
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
+ logArgs(packageManager, "create", argsFormatted, [cwd ?? defaultPath]);
1582
+ await addPnpmBuildDependencies(projectPath, packageManager, ["esbuild"]);
1583
+ if (packageManager) await installDependencies(packageManager, projectPath);
1461
1584
  return {
1462
1585
  directory: projectPath,
1463
1586
  addOnNextSteps,
@@ -1483,6 +1606,28 @@ async function confirmExternalDependencies(dependencies) {
1483
1606
  }
1484
1607
  return installDeps;
1485
1608
  }
1609
+ async function createVirtualWorkspace({ cwd, template, packageManager, type }) {
1610
+ const tentativeWorkspace = await createWorkspace({
1611
+ cwd,
1612
+ packageManager
1613
+ });
1614
+ const virtualWorkspace = {
1615
+ ...tentativeWorkspace,
1616
+ typescript: type === "typescript",
1617
+ files: {
1618
+ ...tentativeWorkspace.files,
1619
+ viteConfig: type === "typescript" ? commonFilePaths.viteConfigTS : commonFilePaths.viteConfig,
1620
+ svelteConfig: type === "typescript" ? commonFilePaths.svelteConfigTS : commonFilePaths.svelteConfig
1621
+ },
1622
+ kit: void 0,
1623
+ dependencyVersion: () => void 0
1624
+ };
1625
+ if (template === "minimal" || template === "demo" || template === "library") virtualWorkspace.kit = {
1626
+ routesDirectory: "src/routes",
1627
+ libDirectory: "src/lib"
1628
+ };
1629
+ return virtualWorkspace;
1630
+ }
1486
1631
 
1487
1632
  //#endregion
1488
1633
  //#region commands/migrate.ts