@stencil/cli 5.0.0-alpha.4 → 5.0.0-alpha.5

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/index.d.mts CHANGED
@@ -11,7 +11,7 @@ type TaskCommand = 'build' | 'docs' | 'generate' | 'g' | 'help' | 'info' | 'migr
11
11
  /**
12
12
  * All the Boolean options supported by the Stencil CLI
13
13
  */
14
- declare const BOOLEAN_CLI_FLAGS: readonly ["build", "cache", "checkVersion", "ci", "compare", "debug", "dev", "devtools", "docs", "dryRun", "esm", "help", "log", "open", "prerender", "prerenderExternal", "prod", "profile", "serviceWorker", "serve", "skipNodeCheck", "ssr", "verbose", "version", "watch"];
14
+ declare const BOOLEAN_CLI_FLAGS: readonly ["build", "cache", "checkVersion", "ci", "compare", "debug", "dev", "devtools", "docs", "dryRun", "help", "log", "open", "prerender", "prerenderExternal", "profile", "serviceWorker", "serve", "skipNodeCheck", "ssr", "verbose", "version", "watch"];
15
15
  /**
16
16
  * All the Number options supported by the Stencil CLI
17
17
  */
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { LOG_LEVELS } from "@stencil/core/compiler";
2
- import { buildError, catchError, hasError, isFunction, isOutputTargetDocs, isOutputTargetHydrate, isOutputTargetWww, isString, normalizePath, readOnlyArrayHasStringMember, result, shouldIgnoreError, toCamelCase, validateComponentTag } from "@stencil/core/compiler/utils";
2
+ import { buildError, catchError, hasError, isFunction, isOutputTargetDocs, isOutputTargetSsr, isOutputTargetWww, isString, normalizePath, readOnlyArrayHasStringMember, result, shouldIgnoreError, toCamelCase, validateComponentTag } from "@stencil/core/compiler/utils";
3
3
  import { isAbsolute, join, parse, relative } from "path";
4
4
  import ts from "typescript";
5
5
  import { start } from "@stencil/dev-server";
@@ -18,13 +18,11 @@ const BOOLEAN_CLI_FLAGS = [
18
18
  "devtools",
19
19
  "docs",
20
20
  "dryRun",
21
- "esm",
22
21
  "help",
23
22
  "log",
24
23
  "open",
25
24
  "prerender",
26
25
  "prerenderExternal",
27
- "prod",
28
26
  "profile",
29
27
  "serviceWorker",
30
28
  "serve",
@@ -561,7 +559,7 @@ const startupCompilerLog = (coreCompiler, config) => {
561
559
  *
562
560
  * This function applies command-line flags to the config, with CLI flags
563
561
  * taking precedence over config file values. This is the canonical place
564
- * where flag values are translated into config properties.?
562
+ * where flag values are translated into config properties.
565
563
  *
566
564
  * @param config The config object (from stencil.config.ts or empty)
567
565
  * @param flags The parsed CLI flags
@@ -569,13 +567,11 @@ const startupCompilerLog = (coreCompiler, config) => {
569
567
  */
570
568
  const mergeFlags = (config, flags) => {
571
569
  const merged = { ...config };
572
- if (flags.prod === true) merged.devMode = false;
573
- else if (flags.dev === true) merged.devMode = true;
570
+ if (flags.dev === true) merged.devMode = true;
574
571
  if (flags.debug === true || flags.verbose === true) merged.logLevel = "debug";
575
572
  else if (flags.logLevel) merged.logLevel = flags.logLevel;
576
573
  if (typeof flags.watch === "boolean") merged.watch = flags.watch;
577
- if (typeof flags.docs === "boolean") merged.buildDocs = flags.docs;
578
- if (typeof flags.esm === "boolean") merged.buildDist = flags.esm;
574
+ if (flags.docs === true) merged._docsFlag = true;
579
575
  if (typeof flags.profile === "boolean") merged.profile = flags.profile;
580
576
  if (typeof flags.log === "boolean") merged.writeLog = flags.log;
581
577
  if (typeof flags.cache === "boolean") merged.enableCache = flags.cache;
@@ -618,6 +614,164 @@ const printCheckVersionResults = async (versionChecker) => {
618
614
  }
619
615
  };
620
616
  //#endregion
617
+ //#region src/migrations/rules/build-dist-docs.ts
618
+ /**
619
+ * Migration rule for buildDist and buildDocs removal.
620
+ *
621
+ * In Stencil v5, these global config options are replaced with per-output-target
622
+ * `skipInDev` property. This migration:
623
+ * - Detects `buildDist` or `buildDocs` in stencil.config.ts
624
+ * - Removes the property
625
+ * - For `buildDist: true` or `buildDocs: true`, adds `skipInDev: false` to relevant output targets
626
+ */
627
+ const buildDistDocsRule = {
628
+ id: "build-dist-docs",
629
+ name: "buildDist/buildDocs Removal",
630
+ description: "Remove deprecated buildDist and buildDocs config options",
631
+ fromVersion: "4.x",
632
+ toVersion: "5.x",
633
+ detect(sourceFile) {
634
+ const matches = [];
635
+ const visit = (node) => {
636
+ if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name)) {
637
+ const propName = node.name.text;
638
+ if (propName === "buildDist" || propName === "buildDocs") {
639
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
640
+ const isTrue = node.initializer.kind === ts.SyntaxKind.TrueKeyword;
641
+ matches.push({
642
+ node,
643
+ message: `Deprecated '${propName}' found${isTrue ? " (set to true)" : ""} - will be removed and skipInDev added to output targets`,
644
+ line: line + 1,
645
+ column: character + 1
646
+ });
647
+ }
648
+ }
649
+ ts.forEachChild(node, visit);
650
+ };
651
+ visit(sourceFile);
652
+ return matches;
653
+ },
654
+ transform(sourceFile, matches) {
655
+ if (matches.length === 0) return sourceFile.getFullText();
656
+ let text = sourceFile.getFullText();
657
+ const targetTypesNeedingSkipInDev = [];
658
+ for (const match of matches) {
659
+ const prop = match.node;
660
+ const propName = prop.name.text;
661
+ if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword) {
662
+ if (propName === "buildDist") targetTypesNeedingSkipInDev.push("dist", "dist-custom-elements", "dist-hydrate-script", "loader-bundle", "standalone", "ssr");
663
+ else if (propName === "buildDocs") targetTypesNeedingSkipInDev.push("docs-readme", "docs-json", "docs-custom", "docs-vscode", "docs-custom-elements-manifest");
664
+ }
665
+ }
666
+ const outputTargetsToModify = [];
667
+ const findOutputTargets = (node) => {
668
+ if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === "outputTargets" && ts.isArrayLiteralExpression(node.initializer)) {
669
+ for (const element of node.initializer.elements) if (ts.isObjectLiteralExpression(element)) {
670
+ const typeProp = element.properties.find((p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === "type");
671
+ if (typeProp && ts.isStringLiteral(typeProp.initializer)) {
672
+ const targetType = typeProp.initializer.text;
673
+ if (targetTypesNeedingSkipInDev.includes(targetType)) {
674
+ if (!element.properties.some((p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === "skipInDev")) outputTargetsToModify.push(element);
675
+ }
676
+ }
677
+ }
678
+ }
679
+ ts.forEachChild(node, findOutputTargets);
680
+ };
681
+ findOutputTargets(sourceFile);
682
+ const allModifications = [];
683
+ for (const match of matches) {
684
+ const prop = match.node;
685
+ const start = prop.getStart();
686
+ let end = prop.getEnd();
687
+ const afterProp = text.slice(end).match(/^\s*,/);
688
+ if (afterProp) end = end + afterProp[0].length;
689
+ allModifications.push({
690
+ type: "remove",
691
+ start,
692
+ end,
693
+ node: prop
694
+ });
695
+ }
696
+ for (const target of outputTargetsToModify) {
697
+ const lastProp = target.properties[target.properties.length - 1];
698
+ if (lastProp) allModifications.push({
699
+ type: "addSkipInDev",
700
+ start: lastProp.getEnd(),
701
+ end: lastProp.getEnd(),
702
+ node: target
703
+ });
704
+ }
705
+ allModifications.sort((a, b) => b.start - a.start);
706
+ for (const mod of allModifications) if (mod.type === "remove") text = text.slice(0, mod.start) + text.slice(mod.end);
707
+ else if (mod.type === "addSkipInDev") {
708
+ const firstProp = mod.node.properties[0];
709
+ let indent = " ";
710
+ if (firstProp) {
711
+ const propStart = firstProp.getStart();
712
+ const lineStart = text.lastIndexOf("\n", propStart) + 1;
713
+ const leadingWhitespace = text.slice(lineStart, propStart);
714
+ if (/^\s+$/.test(leadingWhitespace)) indent = leadingWhitespace;
715
+ }
716
+ const afterLastProp = text.slice(mod.start).match(/^(\s*,)?/);
717
+ const skipLength = afterLastProp && afterLastProp[1] ? afterLastProp[0].length : 0;
718
+ text = text.slice(0, mod.start) + `,\n${indent}skipInDev: false,` + text.slice(mod.start + skipLength);
719
+ }
720
+ text = text.replace(/,\s*\n\s*\n/g, ",\n");
721
+ text = text.replace(/{\s*\n\s*\n/g, "{\n");
722
+ return text;
723
+ }
724
+ };
725
+ //#endregion
726
+ //#region src/migrations/rules/dev-mode.ts
727
+ /**
728
+ * Migration rule for `devMode` config option removal.
729
+ *
730
+ * In Stencil v5, `devMode` is no longer a user-settable config option in `stencil.config.ts`.
731
+ * Build mode is controlled exclusively by the `--dev` CLI flag (default is production).
732
+ * This migration removes `devMode` from the config object.
733
+ */
734
+ const devModeRule = {
735
+ id: "dev-mode",
736
+ name: "devMode Config Removal",
737
+ description: "Remove deprecated devMode config option - use --dev CLI flag instead",
738
+ fromVersion: "4.x",
739
+ toVersion: "5.x",
740
+ detect(sourceFile) {
741
+ const matches = [];
742
+ const visit = (node) => {
743
+ if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name)) {
744
+ if (node.name.text === "devMode") {
745
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
746
+ matches.push({
747
+ node,
748
+ message: `Deprecated 'devMode' config option found - build mode is now set via the --dev CLI flag (production is the default)`,
749
+ line: line + 1,
750
+ column: character + 1
751
+ });
752
+ }
753
+ }
754
+ ts.forEachChild(node, visit);
755
+ };
756
+ visit(sourceFile);
757
+ return matches;
758
+ },
759
+ transform(sourceFile, matches) {
760
+ if (matches.length === 0) return sourceFile.getFullText();
761
+ let text = sourceFile.getFullText();
762
+ for (const match of [...matches].reverse()) {
763
+ const node = match.node;
764
+ const start = node.getFullStart();
765
+ const end = node.getEnd();
766
+ let removeEnd = end;
767
+ const trailingComma = text.slice(end).match(/^\s*,/);
768
+ if (trailingComma) removeEnd = end + trailingComma[0].length;
769
+ text = text.slice(0, start) + text.slice(removeEnd);
770
+ }
771
+ return text;
772
+ }
773
+ };
774
+ //#endregion
621
775
  //#region src/migrations/rules/encapsulation-api.ts
622
776
  /**
623
777
  * Migration rule for the @Component encapsulation API change.
@@ -820,6 +974,376 @@ const formAssociatedRule = {
820
974
  }
821
975
  };
822
976
  //#endregion
977
+ //#region src/migrations/rules/global-style-inject.ts
978
+ /**
979
+ * Migration rule for `extras.addGlobalStyleToComponents` → `global-style` output target `inject`.
980
+ *
981
+ * In v5, the `addGlobalStyleToComponents` extras option is removed. Instead, configure
982
+ * the `inject` option on the `global-style` output target.
983
+ *
984
+ * Migration mapping:
985
+ * - `addGlobalStyleToComponents: true` → `inject: 'all'`
986
+ * - `addGlobalStyleToComponents: 'client'` → `inject: 'client'`
987
+ * - `addGlobalStyleToComponents: false` → omit (default is 'none')
988
+ *
989
+ * Note: This migration only acts on explicitly set `addGlobalStyleToComponents`.
990
+ * Users who relied on the implicit v4 default ('client') will need to manually
991
+ * add `inject: 'client'` to their global-style output target if desired.
992
+ */
993
+ const globalStyleInjectRule = {
994
+ id: "global-style-inject",
995
+ name: "Global Style Inject Migration",
996
+ description: "Migrate extras.addGlobalStyleToComponents to global-style output target inject option",
997
+ fromVersion: "4.x",
998
+ toVersion: "5.x",
999
+ detect(sourceFile) {
1000
+ const matches = [];
1001
+ const visit = (node) => {
1002
+ if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === "addGlobalStyleToComponents") {
1003
+ const parent = node.parent;
1004
+ if (ts.isObjectLiteralExpression(parent)) {
1005
+ const grandparent = parent.parent;
1006
+ if (ts.isPropertyAssignment(grandparent) && ts.isIdentifier(grandparent.name) && grandparent.name.text === "extras") {
1007
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
1008
+ matches.push({
1009
+ node,
1010
+ message: `extras.addGlobalStyleToComponents is removed. Use 'inject' on global-style output target instead.`,
1011
+ line: line + 1,
1012
+ column: character + 1
1013
+ });
1014
+ }
1015
+ }
1016
+ }
1017
+ ts.forEachChild(node, visit);
1018
+ };
1019
+ visit(sourceFile);
1020
+ return matches;
1021
+ },
1022
+ transform(sourceFile, matches) {
1023
+ if (matches.length === 0) return sourceFile.getFullText();
1024
+ let text = sourceFile.getFullText();
1025
+ const matchNode = matches[0].node;
1026
+ if (!ts.isPropertyAssignment(matchNode)) return text;
1027
+ let injectValue = null;
1028
+ if (matchNode.initializer.kind === ts.SyntaxKind.TrueKeyword) injectValue = "all";
1029
+ else if (ts.isStringLiteral(matchNode.initializer) && matchNode.initializer.text === "client") injectValue = "client";
1030
+ let insertionOffset = 0;
1031
+ if (injectValue) {
1032
+ const result = addGlobalStyleOutputTarget(text, sourceFile, injectValue);
1033
+ insertionOffset = result.length - text.length;
1034
+ text = result;
1035
+ }
1036
+ let start = matchNode.getStart();
1037
+ let end = matchNode.getEnd();
1038
+ const outputTargetsEnd = findOutputTargetsEnd(sourceFile);
1039
+ if (insertionOffset !== 0 && outputTargetsEnd !== null && matchNode.getStart() > outputTargetsEnd) {
1040
+ start += insertionOffset;
1041
+ end += insertionOffset;
1042
+ }
1043
+ const afterProp = text.slice(end).match(/^\s*,/);
1044
+ if (afterProp) end = end + afterProp[0].length;
1045
+ else {
1046
+ const beforeProp = text.slice(0, start).match(/,\s*$/);
1047
+ if (beforeProp) {
1048
+ text = text.slice(0, start - beforeProp[0].length) + text.slice(end);
1049
+ end = -1;
1050
+ }
1051
+ }
1052
+ if (end !== -1) text = text.slice(0, start) + text.slice(end);
1053
+ text = cleanupEmptyExtras(text);
1054
+ return text;
1055
+ }
1056
+ };
1057
+ /**
1058
+ * Find the end position of the outputTargets array in the source file.
1059
+ * @param sourceFile The source file to search
1060
+ * @returns The end position of the outputTargets array, or null if not found
1061
+ */
1062
+ function findOutputTargetsEnd(sourceFile) {
1063
+ let endPos = null;
1064
+ const visit = (node) => {
1065
+ if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === "outputTargets") endPos = node.getEnd();
1066
+ ts.forEachChild(node, visit);
1067
+ };
1068
+ visit(sourceFile);
1069
+ return endPos;
1070
+ }
1071
+ /**
1072
+ * Clean up empty extras object after removing addGlobalStyleToComponents.
1073
+ * @param text The source text to clean up
1074
+ * @returns The cleaned up text with empty extras removed
1075
+ */
1076
+ function cleanupEmptyExtras(text) {
1077
+ return text.replace(/,?\s*extras\s*:\s*\{\s*\},?/g, "");
1078
+ }
1079
+ /**
1080
+ * Add a global-style output target with the specified inject value.
1081
+ * @param text The source text to modify
1082
+ * @param sourceFile The source file for position info
1083
+ * @param injectValue The inject value to set on the new global-style output target
1084
+ * @returns The modified text with the new global-style output target added
1085
+ */
1086
+ function addGlobalStyleOutputTarget(text, sourceFile, injectValue) {
1087
+ if (text.includes("type: 'global-style'")) return text;
1088
+ let outputTargetsArray = null;
1089
+ const findOutputTargets = (node) => {
1090
+ if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === "outputTargets" && ts.isArrayLiteralExpression(node.initializer)) outputTargetsArray = node.initializer;
1091
+ ts.forEachChild(node, findOutputTargets);
1092
+ };
1093
+ findOutputTargets(sourceFile);
1094
+ if (!outputTargetsArray) return text;
1095
+ const array = outputTargetsArray;
1096
+ let indent = " ";
1097
+ if (array.elements.length > 0) {
1098
+ const elemStart = array.elements[0].getStart();
1099
+ const lineStart = text.lastIndexOf("\n", elemStart) + 1;
1100
+ const leadingWhitespace = text.slice(lineStart, elemStart);
1101
+ if (/^\s+$/.test(leadingWhitespace)) indent = leadingWhitespace;
1102
+ }
1103
+ const lastElement = array.elements[array.elements.length - 1];
1104
+ if (!lastElement) {
1105
+ const arrayStart = array.getStart() + 1;
1106
+ const newTarget = `\n${indent}{\n${indent} type: 'global-style',\n${indent} inject: '${injectValue}',\n${indent}}\n${indent.slice(2)}`;
1107
+ return text.slice(0, arrayStart) + newTarget + text.slice(arrayStart);
1108
+ }
1109
+ let insertPos = lastElement.getEnd();
1110
+ const afterLastElement = text.slice(insertPos).match(/^(\s*,)?/);
1111
+ const hasTrailingComma = afterLastElement && afterLastElement[1];
1112
+ if (hasTrailingComma) insertPos += afterLastElement[0].length;
1113
+ const newTarget = `{\n${indent} type: 'global-style',\n${indent} inject: '${injectValue}',\n${indent}}`;
1114
+ const insertion = (hasTrailingComma ? "" : ",") + "\n" + indent + newTarget;
1115
+ return text.slice(0, insertPos) + insertion + text.slice(insertPos);
1116
+ }
1117
+ //#endregion
1118
+ //#region src/migrations/rules/output-target-renames.ts
1119
+ /**
1120
+ * Migration rule for output target renames in Stencil v5.
1121
+ *
1122
+ * This migration:
1123
+ * - Renames `dist` → `loader-bundle`
1124
+ * - Renames `dist-custom-elements` → `standalone`
1125
+ * - Renames `dist-hydrate-script` → `ssr`
1126
+ * - Renames `dist-collection` → `stencil-rebundle`
1127
+ * - Renames `dist-types` → `types`
1128
+ * - Extracts `collectionDir` from loader-bundle into separate `stencil-rebundle` output
1129
+ * - Extracts `typesDir` from loader-bundle into separate `types` output
1130
+ * - Renames `esmLoaderPath` → `loaderPath` (applies to all module formats, not just ESM)
1131
+ * - Removes `isPrimaryPackageOutputTarget` (no longer needed, package.json validation auto-detects)
1132
+ * - Removes `generateTypeDeclarations` (types are now auto-generated via the `types` output target)
1133
+ */
1134
+ const outputTargetRenamesRule = {
1135
+ id: "output-target-renames",
1136
+ name: "Output Target Renames",
1137
+ description: "Rename output targets to new v5 names",
1138
+ fromVersion: "4.x",
1139
+ toVersion: "5.x",
1140
+ detect(sourceFile) {
1141
+ const matches = [];
1142
+ const oldToNewNames = {
1143
+ dist: "loader-bundle",
1144
+ "dist-custom-elements": "standalone",
1145
+ "dist-hydrate-script": "ssr",
1146
+ "dist-collection": "stencil-rebundle",
1147
+ "dist-types": "types"
1148
+ };
1149
+ const visit = (node) => {
1150
+ if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === "type" && ts.isStringLiteral(node.initializer)) {
1151
+ const typeValue = node.initializer.text;
1152
+ if (typeValue in oldToNewNames) {
1153
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
1154
+ matches.push({
1155
+ node,
1156
+ message: `Output target type '${typeValue}' → '${oldToNewNames[typeValue]}'`,
1157
+ line: line + 1,
1158
+ column: character + 1
1159
+ });
1160
+ }
1161
+ }
1162
+ if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && (node.name.text === "collectionDir" || node.name.text === "typesDir")) {
1163
+ const parent = node.parent;
1164
+ if (ts.isObjectLiteralExpression(parent)) {
1165
+ if (parent.properties.some((p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === "type")) {
1166
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
1167
+ const propName = node.name.text;
1168
+ const newType = propName === "collectionDir" ? "stencil-rebundle" : "types";
1169
+ matches.push({
1170
+ node,
1171
+ message: `Property '${propName}' will be extracted to separate '${newType}' output target`,
1172
+ line: line + 1,
1173
+ column: character + 1
1174
+ });
1175
+ }
1176
+ }
1177
+ }
1178
+ if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === "esmLoaderPath") {
1179
+ const parent = node.parent;
1180
+ if (ts.isObjectLiteralExpression(parent)) {
1181
+ if (parent.properties.some((p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === "type")) {
1182
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
1183
+ matches.push({
1184
+ node,
1185
+ message: `Property 'esmLoaderPath' renamed to 'loaderPath' (applies to all module formats)`,
1186
+ line: line + 1,
1187
+ column: character + 1
1188
+ });
1189
+ }
1190
+ }
1191
+ }
1192
+ if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && (node.name.text === "isPrimaryPackageOutputTarget" || node.name.text === "generateTypeDeclarations")) {
1193
+ const parent = node.parent;
1194
+ if (ts.isObjectLiteralExpression(parent)) {
1195
+ if (parent.properties.some((p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === "type")) {
1196
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
1197
+ const propName = node.name.text;
1198
+ const reason = propName === "isPrimaryPackageOutputTarget" ? "Package.json validation now auto-detects based on configured outputs" : "Types are now auto-generated via the 'types' output target";
1199
+ matches.push({
1200
+ node,
1201
+ message: `Property '${propName}' is removed in v5. ${reason}`,
1202
+ line: line + 1,
1203
+ column: character + 1
1204
+ });
1205
+ }
1206
+ }
1207
+ }
1208
+ ts.forEachChild(node, visit);
1209
+ };
1210
+ visit(sourceFile);
1211
+ return matches;
1212
+ },
1213
+ transform(sourceFile, matches) {
1214
+ if (matches.length === 0) return sourceFile.getFullText();
1215
+ let text = sourceFile.getFullText();
1216
+ const oldToNewNames = {
1217
+ dist: "loader-bundle",
1218
+ "dist-custom-elements": "standalone",
1219
+ "dist-hydrate-script": "ssr",
1220
+ "dist-collection": "stencil-rebundle",
1221
+ "dist-types": "types"
1222
+ };
1223
+ const outputTargetsToExtract = [];
1224
+ const findOutputTargetsToExtract = (node) => {
1225
+ if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === "outputTargets" && ts.isArrayLiteralExpression(node.initializer)) {
1226
+ for (const element of node.initializer.elements) if (ts.isObjectLiteralExpression(element)) {
1227
+ let collectionDir;
1228
+ let typesDir;
1229
+ for (const prop of element.properties) if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
1230
+ if (prop.name.text === "collectionDir" && ts.isStringLiteral(prop.initializer)) collectionDir = prop.initializer.text;
1231
+ else if (prop.name.text === "typesDir" && ts.isStringLiteral(prop.initializer)) typesDir = prop.initializer.text;
1232
+ }
1233
+ if (collectionDir || typesDir) outputTargetsToExtract.push({
1234
+ targetNode: element,
1235
+ collectionDir,
1236
+ typesDir
1237
+ });
1238
+ }
1239
+ }
1240
+ ts.forEachChild(node, findOutputTargetsToExtract);
1241
+ };
1242
+ findOutputTargetsToExtract(sourceFile);
1243
+ const modifications = [];
1244
+ for (const match of matches) {
1245
+ const prop = match.node;
1246
+ const propName = prop.name.text;
1247
+ if (propName === "type" && ts.isStringLiteral(prop.initializer)) {
1248
+ const newType = oldToNewNames[prop.initializer.text];
1249
+ if (newType) {
1250
+ const start = prop.initializer.getStart() + 1;
1251
+ const end = prop.initializer.getEnd() - 1;
1252
+ modifications.push({
1253
+ type: "replace",
1254
+ start,
1255
+ end,
1256
+ replacement: newType
1257
+ });
1258
+ }
1259
+ } else if (propName === "esmLoaderPath") {
1260
+ const nameNode = prop.name;
1261
+ modifications.push({
1262
+ type: "replace",
1263
+ start: nameNode.getStart(),
1264
+ end: nameNode.getEnd(),
1265
+ replacement: "loaderPath"
1266
+ });
1267
+ if (ts.isStringLiteral(prop.initializer)) {
1268
+ const newPath = "../" + prop.initializer.text;
1269
+ modifications.push({
1270
+ type: "replace",
1271
+ start: prop.initializer.getStart() + 1,
1272
+ end: prop.initializer.getEnd() - 1,
1273
+ replacement: newPath
1274
+ });
1275
+ }
1276
+ } else if (propName === "collectionDir" || propName === "typesDir" || propName === "isPrimaryPackageOutputTarget" || propName === "generateTypeDeclarations") {
1277
+ const start = prop.getStart();
1278
+ let end = prop.getEnd();
1279
+ const beforeProp = text.slice(0, start).match(/,\s*$/);
1280
+ const afterProp = text.slice(end).match(/^\s*,/);
1281
+ if (afterProp) end = end + afterProp[0].length;
1282
+ else if (beforeProp) {
1283
+ const commaStart = start - beforeProp[0].length;
1284
+ modifications.push({
1285
+ type: "remove",
1286
+ start: commaStart,
1287
+ end
1288
+ });
1289
+ continue;
1290
+ }
1291
+ modifications.push({
1292
+ type: "remove",
1293
+ start,
1294
+ end
1295
+ });
1296
+ }
1297
+ }
1298
+ modifications.sort((a, b) => b.start - a.start);
1299
+ for (const mod of modifications) if (mod.type === "replace") text = text.slice(0, mod.start) + mod.replacement + text.slice(mod.end);
1300
+ else if (mod.type === "remove") text = text.slice(0, mod.start) + text.slice(mod.end);
1301
+ if (outputTargetsToExtract.length > 0) text = addExtractedOutputTargets(text, sourceFile, outputTargetsToExtract);
1302
+ text = text.replace(/,\s*\n\s*\n/g, ",\n");
1303
+ text = text.replace(/{\s*\n\s*\n/g, "{\n");
1304
+ text = text.replace(/,\s*,/g, ",");
1305
+ return text;
1306
+ }
1307
+ };
1308
+ /**
1309
+ * Add new output targets for extracted collectionDir and typesDir.
1310
+ * @param text The original source text
1311
+ * @param sourceFile The TypeScript source file object
1312
+ * @param toExtract An array of objects containing the output target nodes and their collectionDir/typesDir values to extract
1313
+ * @returns The modified source text with new output targets added
1314
+ */
1315
+ function addExtractedOutputTargets(text, sourceFile, toExtract) {
1316
+ let outputTargetsArray = null;
1317
+ const findOutputTargets = (node) => {
1318
+ if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === "outputTargets" && ts.isArrayLiteralExpression(node.initializer)) outputTargetsArray = node.initializer;
1319
+ ts.forEachChild(node, findOutputTargets);
1320
+ };
1321
+ findOutputTargets(sourceFile);
1322
+ if (!outputTargetsArray || toExtract.length === 0) return text;
1323
+ const array = outputTargetsArray;
1324
+ let indent = " ";
1325
+ if (array.elements.length > 0) {
1326
+ const elemStart = array.elements[0].getStart();
1327
+ const lineStart = text.lastIndexOf("\n", elemStart) + 1;
1328
+ const leadingWhitespace = text.slice(lineStart, elemStart);
1329
+ if (/^\s+$/.test(leadingWhitespace)) indent = leadingWhitespace;
1330
+ }
1331
+ const lastElement = array.elements[array.elements.length - 1];
1332
+ if (!lastElement) return text;
1333
+ let insertPos = lastElement.getEnd();
1334
+ const afterLastElement = text.slice(insertPos).match(/^(\s*,)?/);
1335
+ if (afterLastElement && afterLastElement[1]) insertPos += afterLastElement[0].length;
1336
+ const newTargets = [];
1337
+ for (const extracted of toExtract) {
1338
+ if (extracted.collectionDir) newTargets.push(`{\n${indent} type: 'stencil-rebundle',\n${indent} dir: '${extracted.collectionDir}',\n${indent}}`);
1339
+ if (extracted.typesDir) newTargets.push(`{\n${indent} type: 'types',\n${indent} dir: '${extracted.typesDir}',\n${indent}}`);
1340
+ }
1341
+ if (newTargets.length === 0) return text;
1342
+ const insertion = ",\n" + newTargets.map((t) => `${indent}${t}`).join(",\n");
1343
+ text = text.slice(0, insertPos) + insertion + text.slice(insertPos);
1344
+ return text;
1345
+ }
1346
+ //#endregion
823
1347
  //#region src/migrations/index.ts
824
1348
  /**
825
1349
  * Build a map of local import names to their original names from @stencil/core.
@@ -857,7 +1381,14 @@ const isStencilDecorator = (decoratorName, expectedOriginalName, importMap) => {
857
1381
  * Registry of all available migration rules.
858
1382
  * Rules are applied in order, so add new rules at the end.
859
1383
  */
860
- const migrationRules = [encapsulationApiRule, formAssociatedRule];
1384
+ const migrationRules = [
1385
+ encapsulationApiRule,
1386
+ formAssociatedRule,
1387
+ buildDistDocsRule,
1388
+ outputTargetRenamesRule,
1389
+ devModeRule,
1390
+ globalStyleInjectRule
1391
+ ];
861
1392
  /**
862
1393
  * Get all migration rules for a specific version upgrade.
863
1394
  * @param fromVersion Source version (e.g., '4')
@@ -1041,27 +1572,32 @@ async function getTypeScriptFiles(config, sys, logger) {
1041
1572
  return [];
1042
1573
  }
1043
1574
  if (results.errors && results.errors.length > 0) for (const err of results.errors) logger.warn(ts.flattenDiagnosticMessageText(err.messageText, "\n"));
1044
- return results.fileNames.filter((f) => (f.endsWith(".ts") || f.endsWith(".tsx")) && !f.endsWith(".d.ts"));
1575
+ const files = results.fileNames.filter((f) => (f.endsWith(".ts") || f.endsWith(".tsx")) && !f.endsWith(".d.ts"));
1576
+ const configFile = config.configPath;
1577
+ if (configFile && (configFile.endsWith(".ts") || configFile.endsWith(".mts"))) {
1578
+ if (!files.includes(configFile)) files.push(configFile);
1579
+ }
1580
+ return files;
1045
1581
  }
1046
1582
  //#endregion
1047
1583
  //#region src/task-prerender.ts
1048
1584
  const taskPrerender = async (coreCompiler, config, flags) => {
1049
1585
  startupCompilerLog(coreCompiler, config);
1050
- const hydrateAppFilePath = flags.unknownArgs[0];
1051
- if (typeof hydrateAppFilePath !== "string") {
1586
+ const ssrAppFilePath = flags.unknownArgs[0];
1587
+ if (typeof ssrAppFilePath !== "string") {
1052
1588
  config.logger.error(`Missing hydrate app script path`);
1053
1589
  return config.sys.exit(1);
1054
1590
  }
1055
1591
  const srcIndexHtmlPath = config.srcIndexHtml;
1056
- const diagnostics = await runPrerenderTask(coreCompiler, config, hydrateAppFilePath, void 0, srcIndexHtmlPath);
1592
+ const diagnostics = await runPrerenderTask(coreCompiler, config, ssrAppFilePath, void 0, srcIndexHtmlPath);
1057
1593
  config.logger.printDiagnostics(diagnostics);
1058
1594
  if (diagnostics.some((d) => d.level === "error")) return config.sys.exit(1);
1059
1595
  };
1060
- const runPrerenderTask = async (coreCompiler, config, hydrateAppFilePath, componentGraph, srcIndexHtmlPath) => {
1596
+ const runPrerenderTask = async (coreCompiler, config, ssrAppFilePath, componentGraph, srcIndexHtmlPath) => {
1061
1597
  const diagnostics = [];
1062
1598
  try {
1063
1599
  const results = await (await coreCompiler.createPrerenderer(config)).start({
1064
- hydrateAppFilePath,
1600
+ ssrAppFilePath,
1065
1601
  componentGraph,
1066
1602
  srcIndexHtmlPath
1067
1603
  });
@@ -1393,7 +1929,7 @@ const anonymizeConfigForTelemetry = (config) => {
1393
1929
  if (OUTPUT_TARGET_KEYS_TO_KEEP.includes(key)) return value;
1394
1930
  return "omitted";
1395
1931
  }));
1396
- if (isOutputTargetHydrate(target) && target.external) anonymizedOT["external"] = target.external.concat();
1932
+ if (isOutputTargetSsr(target) && target.external) anonymizedOT["external"] = target.external.concat();
1397
1933
  return anonymizedOT;
1398
1934
  });
1399
1935
  for (const prop of CONFIG_PROPS_TO_DELETE) delete anonymizedConfig[prop];
@@ -1587,6 +2123,23 @@ const taskBuild = async (coreCompiler, config, flags) => {
1587
2123
  let exitCode = 0;
1588
2124
  try {
1589
2125
  startupCompilerLog(coreCompiler, config);
2126
+ const preBuildMigrationResult = await detectMigrations(coreCompiler, config);
2127
+ if (preBuildMigrationResult.hasMigrations) {
2128
+ const action = await promptForMigration(config, preBuildMigrationResult, "pre-build");
2129
+ if (action === "run") {
2130
+ await taskMigrate(coreCompiler, config, {
2131
+ ...flags,
2132
+ dryRun: false
2133
+ });
2134
+ config.logger.info("\nMigrations applied. Starting build...\n");
2135
+ } else if (action === "dry-run") {
2136
+ await taskMigrate(coreCompiler, config, {
2137
+ ...flags,
2138
+ dryRun: true
2139
+ });
2140
+ return config.sys.exit(1);
2141
+ } else return config.sys.exit(1);
2142
+ }
1590
2143
  const versionChecker = startCheckVersion(config, coreCompiler.version, flags);
1591
2144
  const compiler = await coreCompiler.createCompiler(config);
1592
2145
  const results = await compiler.build();
@@ -1595,7 +2148,7 @@ const taskBuild = async (coreCompiler, config, flags) => {
1595
2148
  if (results.hasError) {
1596
2149
  const migrationResult = await detectMigrations(coreCompiler, config);
1597
2150
  if (migrationResult.hasMigrations) {
1598
- const action = await promptForMigrationOnBuildError(config, migrationResult);
2151
+ const action = await promptForMigration(config, migrationResult, "post-error");
1599
2152
  if (action === "run") {
1600
2153
  await taskMigrate(coreCompiler, config, {
1601
2154
  ...flags,
@@ -1608,7 +2161,7 @@ const taskBuild = async (coreCompiler, config, flags) => {
1608
2161
  if (!newResults.hasError) {
1609
2162
  exitCode = 0;
1610
2163
  if (flags.prerender) {
1611
- const prerenderDiagnostics = await runPrerenderTask(coreCompiler, config, newResults.hydrateAppFilePath, newResults.componentGraph, void 0);
2164
+ const prerenderDiagnostics = await runPrerenderTask(coreCompiler, config, newResults.ssrAppFilePath, newResults.componentGraph, void 0);
1612
2165
  config.logger.printDiagnostics(prerenderDiagnostics);
1613
2166
  if (prerenderDiagnostics.some((d) => d.level === "error")) exitCode = 1;
1614
2167
  }
@@ -1622,7 +2175,7 @@ const taskBuild = async (coreCompiler, config, flags) => {
1622
2175
  } else exitCode = 1;
1623
2176
  } else exitCode = 1;
1624
2177
  } else if (flags.prerender) {
1625
- const prerenderDiagnostics = await runPrerenderTask(coreCompiler, config, results.hydrateAppFilePath, results.componentGraph, void 0);
2178
+ const prerenderDiagnostics = await runPrerenderTask(coreCompiler, config, results.ssrAppFilePath, results.componentGraph, void 0);
1626
2179
  config.logger.printDiagnostics(prerenderDiagnostics);
1627
2180
  if (prerenderDiagnostics.some((d) => d.level === "error")) exitCode = 1;
1628
2181
  }
@@ -1634,24 +2187,26 @@ const taskBuild = async (coreCompiler, config, flags) => {
1634
2187
  if (exitCode > 0) return config.sys.exit(exitCode);
1635
2188
  };
1636
2189
  /**
1637
- * Prompt the user about available migrations when a build fails.
2190
+ * Prompt the user about available migrations.
1638
2191
  * Shows what migrations are available and lets them choose to run them.
1639
2192
  * @param config the Stencil config
1640
2193
  * @param migrationResult the result of migration detection with available migrations
2194
+ * @param context whether this is a pre-build check or post-error check
1641
2195
  * @returns the user's chosen action for handling migrations
1642
2196
  */
1643
- async function promptForMigrationOnBuildError(config, migrationResult) {
2197
+ async function promptForMigration(config, migrationResult, context) {
1644
2198
  const logger = config.logger;
1645
2199
  logger.info("");
1646
- logger.info(logger.bold(logger.yellow("Migrations Available")));
2200
+ logger.info(logger.bold(logger.yellow("Migrations Required")));
1647
2201
  logger.info("─".repeat(40));
1648
- logger.info(`Found ${migrationResult.totalMatches} item(s) in ${migrationResult.filesAffected} file(s) that can be automatically migrated.`);
2202
+ logger.info(`Found ${migrationResult.totalMatches} item(s) in ${migrationResult.filesAffected} file(s) that need to be migrated for Stencil v5.`);
1649
2203
  for (const migration of migrationResult.migrations) {
1650
2204
  const relPath = relative(config.rootDir, migration.filePath);
1651
2205
  logger.info(` ${logger.cyan(relPath)}: ${migration.matches.length} item(s)`);
1652
2206
  }
1653
2207
  logger.info("");
1654
- logger.info("These migrations may help resolve the build errors above.");
2208
+ if (context === "pre-build") logger.info("Your config contains deprecated options that must be migrated before building.");
2209
+ else logger.info("These migrations may help resolve the build errors above.");
1655
2210
  const prompt = (await import("prompts")).default;
1656
2211
  const response = await prompt({
1657
2212
  name: "action",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stencil/cli",
3
- "version": "5.0.0-alpha.4",
3
+ "version": "5.0.0-alpha.5",
4
4
  "description": "CLI for Stencil - Web component compiler",
5
5
  "keywords": [
6
6
  "components",
@@ -38,14 +38,14 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "prompts": "^2.4.2",
41
- "@stencil/dev-server": "5.0.0-alpha.4"
41
+ "@stencil/dev-server": "5.0.0-alpha.5"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/prompts": "^2.4.9",
45
- "tsdown": "^0.21.6",
46
- "typescript": "~6.0.2",
45
+ "tsdown": "^0.21.7",
46
+ "typescript": ">4.0.0",
47
47
  "vitest": "^4.1.1",
48
- "@stencil/core": "5.0.0-alpha.4"
48
+ "@stencil/core": "5.0.0-alpha.5"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@stencil/core": "^5.0.0-0"