slicejs-cli 3.0.0 → 3.0.3

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/README.md CHANGED
@@ -45,6 +45,29 @@ npm run dev
45
45
  npm run slice -- get Button
46
46
  ```
47
47
 
48
+ 4. Use `slice` directly when the launcher command is available on your system
49
+ (commonly after a global install that puts `slice` in your PATH).
50
+ The launcher delegates to your nearest project-local `node_modules/slicejs-cli`
51
+ so project-pinned behavior is used from the project root and subdirectories.
52
+
53
+ ```bash
54
+ slice dev
55
+ slice build
56
+ slice version
57
+ ```
58
+
59
+ If `slice` is not available in your shell, use:
60
+
61
+ ```bash
62
+ npx slicejs-cli dev
63
+ ```
64
+
65
+ You can disable launcher delegation for a command when needed:
66
+
67
+ ```bash
68
+ SLICE_NO_LOCAL_DELEGATION=1 slice version
69
+ ```
70
+
48
71
  ### Global (Not Recommended)
49
72
 
50
73
  Global installations can lead to version mismatches and "works on my machine" issues.
@@ -55,7 +78,10 @@ npm install -g slicejs-cli
55
78
 
56
79
  ## Usage
57
80
 
58
- After installation, you can use the `slice` command directly:
81
+ After installation, prefer your project-local CLI. When the `slice` launcher command is
82
+ available, it automatically delegates to the nearest local `slicejs-cli` install.
83
+
84
+ Use the `slice` command directly:
59
85
 
60
86
  ```bash
61
87
  slice [command] [options]
@@ -67,6 +93,8 @@ Or with npx (without global install):
67
93
  npx slicejs-cli [command]
68
94
  ```
69
95
 
96
+ Use `npx slicejs-cli [command]` as a fallback when the `slice` launcher command is unavailable.
97
+
70
98
  ## Essential Commands
71
99
 
72
100
  ### Initialize a project
@@ -154,7 +182,7 @@ slice sync
154
182
  ```bash
155
183
  # Version info
156
184
  slice version
157
- slice -v
185
+ slice v
158
186
 
159
187
  # Updates (CLI and Framework)
160
188
  slice update # Check and prompt to update
@@ -325,16 +353,16 @@ slice init
325
353
  ### Command not found
326
354
 
327
355
  ```bash
328
- # Use npx if not installed globally
356
+ # If the launcher command is unavailable, run the local CLI via npx
329
357
  npx slicejs-cli dev
330
358
 
331
- # Or install globally
359
+ # Optional: install globally to expose the slice launcher command
332
360
  npm install -g slicejs-cli
333
361
  ```
334
362
 
335
363
  ## Links
336
364
 
337
- - 📘 Documentation: https://slice-js-docs.vercel.app/
365
+ - 📘 CLI Documentation: https://slice-js-docs.vercel.app/Documentation/CLI
338
366
  - 🐙 GitHub: https://github.com/VKneider/slice-cli
339
367
  - 📦 npm: https://www.npmjs.com/package/slicejs-cli
340
368
 
package/client.js CHANGED
@@ -14,12 +14,18 @@ import fs from "fs";
14
14
  import path from "path";
15
15
  import { fileURLToPath } from "url";
16
16
  import { getConfigPath, getProjectRoot } from "./commands/utils/PathHelper.js";
17
- import { exec } from "child_process";
17
+ import { exec, spawnSync } from "node:child_process";
18
18
  import { promisify } from "util";
19
19
  import validations from "./commands/Validations.js";
20
20
  import Print from "./commands/Print.js";
21
21
  import build from './commands/build/build.js';
22
22
  import { cleanBundles, bundleInfo } from './commands/bundle/bundle.js';
23
+ import {
24
+ isLocalDelegationDisabled,
25
+ findNearestLocalCliEntry,
26
+ resolveLocalCliCandidate,
27
+ shouldDelegateToLocalCli
28
+ } from './commands/utils/LocalCliDelegation.js';
23
29
 
24
30
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
25
31
 
@@ -84,10 +90,7 @@ async function runWithVersionCheck(commandFunction, ...args) {
84
90
  } catch {}
85
91
  })();
86
92
 
87
- const updateInfo = await updateManager.checkForUpdates();
88
- if (updateInfo && updateInfo.hasUpdates) {
89
- await updateManager.checkAndPromptUpdates({});
90
- }
93
+ updateManager.notifyAvailableUpdates().catch(() => {});
91
94
 
92
95
  const result = await commandFunction(...args);
93
96
 
@@ -102,6 +105,33 @@ async function runWithVersionCheck(commandFunction, ...args) {
102
105
  }
103
106
  }
104
107
 
108
+ function maybeDelegateToLocalCli() {
109
+ if (isLocalDelegationDisabled(process.env)) {
110
+ return;
111
+ }
112
+
113
+ const currentEntryPath = fileURLToPath(import.meta.url);
114
+ const localEntryPath = findNearestLocalCliEntry(process.cwd(), resolveLocalCliCandidate);
115
+
116
+ if (!shouldDelegateToLocalCli(currentEntryPath, localEntryPath)) {
117
+ return;
118
+ }
119
+
120
+ const child = spawnSync(
121
+ process.execPath,
122
+ [localEntryPath, ...process.argv.slice(2)],
123
+ {
124
+ stdio: 'inherit',
125
+ cwd: process.cwd(),
126
+ env: process.env
127
+ }
128
+ );
129
+
130
+ process.exit(child.status ?? 1);
131
+ }
132
+
133
+ maybeDelegateToLocalCli();
134
+
105
135
  const sliceClient = program;
106
136
 
107
137
  try {
@@ -0,0 +1,53 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ function getParentDirectory(dir) {
5
+ const parent = path.dirname(dir);
6
+ return parent === dir ? null : parent;
7
+ }
8
+
9
+ export function isLocalDelegationDisabled(env = process.env) {
10
+ return env.SLICE_NO_LOCAL_DELEGATION === '1';
11
+ }
12
+
13
+ export function findNearestLocalCliEntry(startDirectory, resolveCandidate) {
14
+ if (!startDirectory || typeof resolveCandidate !== 'function') {
15
+ return null;
16
+ }
17
+
18
+ let current = path.resolve(startDirectory);
19
+ while (current) {
20
+ const candidate = resolveCandidate(current);
21
+ if (candidate) {
22
+ return candidate;
23
+ }
24
+ current = getParentDirectory(current);
25
+ }
26
+
27
+ return null;
28
+ }
29
+
30
+ export function resolveLocalCliCandidate(directory) {
31
+ const candidate = path.join(directory, 'node_modules', 'slicejs-cli', 'client.js');
32
+
33
+ try {
34
+ const stats = fs.statSync(candidate);
35
+ return stats.isFile() ? candidate : null;
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+
41
+ export function shouldDelegateToLocalCli(currentEntryPath, localEntryPath) {
42
+ if (!localEntryPath) {
43
+ return false;
44
+ }
45
+
46
+ try {
47
+ const currentReal = fs.realpathSync(currentEntryPath);
48
+ const localReal = fs.realpathSync(localEntryPath);
49
+ return currentReal !== localReal;
50
+ } catch {
51
+ return path.resolve(currentEntryPath) !== path.resolve(localEntryPath);
52
+ }
53
+ }
@@ -953,11 +953,14 @@ export default class BundleGenerator {
953
953
  cssContent = await fs.readFile(cssPath, 'utf-8');
954
954
  }
955
955
 
956
+ const cleanedJavaScript = this.cleanJavaScript(jsContent, comp.name, jsPath);
957
+
956
958
  bundleComponents.push({
957
959
  name: comp.name,
958
960
  category: comp.category,
959
961
  categoryType: comp.categoryType,
960
- js: this.cleanJavaScript(jsContent, comp.name),
962
+ js: cleanedJavaScript.code,
963
+ hoistedImports: cleanedJavaScript.hoistedImports,
961
964
  html: htmlContent,
962
965
  css: cssContent,
963
966
  externalDependencies: await this.buildDependencyContents(jsContent, comp.path),
@@ -988,7 +991,22 @@ export default class BundleGenerator {
988
991
  ? 'framework'
989
992
  : this.routeToFileName(routePath || fileName.replace('slice-bundle.', '').replace('.js', ''));
990
993
 
994
+ const dependencyModules = this.collectDependencyModulesFromComponents(uniqueComponents);
991
995
  const dependencyModuleBlock = this.buildV2DependencyModuleBlock(uniqueComponents);
996
+ const rawHoistedImports = uniqueComponents
997
+ .flatMap((component) => component.hoistedImports || [])
998
+ .map((statement) => String(statement).trim())
999
+ .filter(Boolean);
1000
+ const reservedIdentifiers = new Set([
1001
+ 'SLICE_BUNDLE_META',
1002
+ 'SLICE_BUNDLE_DEPENDENCIES',
1003
+ ...uniqueComponents.map((component) => this.classFactoryName(component.name)),
1004
+ ...uniqueComponents.map((component) => `__templateElement_${this.toSafeIdentifier(component.name)}`),
1005
+ ...this.getDependencyExportVariableNames(dependencyModules)
1006
+ ]);
1007
+ this.validateHoistedImportCollisions(rawHoistedImports, reservedIdentifiers);
1008
+ const hoistedImports = Array.from(new Set(rawHoistedImports));
1009
+ const hoistedImportBlock = hoistedImports.join('\n');
992
1010
 
993
1011
  const classFactoryDefinitions = uniqueComponents
994
1012
  .map((component) => {
@@ -1052,23 +1070,14 @@ export default class BundleGenerator {
1052
1070
  componentCount: uniqueComponents.length
1053
1071
  };
1054
1072
 
1055
- return `export const SLICE_BUNDLE_META = ${JSON.stringify(metadata, null, 2)};\n\n${dependencyModuleBlock}\n\n${classFactoryDefinitions}\n\n${templateDeclarations}\n\nexport async function registerAll(controller, stylesManager) {\n${classRegistrations}\n${templateRegistrations}\n${cssRegistrationInit}${cssRegistrationInit ? '\n' : ''}${cssRegistrations}\n${categoryRegistrations}\n}\n`;
1073
+ return `${hoistedImportBlock}${hoistedImportBlock ? '\n\n' : ''}export const SLICE_BUNDLE_META = ${JSON.stringify(metadata, null, 2)};\n\n${dependencyModuleBlock}\n\n${classFactoryDefinitions}\n\n${templateDeclarations}\n\nexport async function registerAll(controller, stylesManager) {\n${classRegistrations}\n${templateRegistrations}\n${cssRegistrationInit}${cssRegistrationInit ? '\n' : ''}${cssRegistrations}\n${categoryRegistrations}\n}\n`;
1056
1074
  }
1057
1075
 
1058
1076
  buildV2DependencyModuleBlock(components) {
1059
- const modules = new Map();
1060
- for (const component of components || []) {
1061
- const externalDependencies = component.externalDependencies || {};
1062
- for (const [moduleName, entry] of Object.entries(externalDependencies)) {
1063
- if (modules.has(moduleName)) continue;
1064
- const content = typeof entry === 'string' ? entry : entry?.content;
1065
- if (!content) continue;
1066
- modules.set(moduleName, { name: moduleName, content });
1067
- }
1068
- }
1077
+ const modules = this.collectDependencyModulesFromComponents(components);
1069
1078
 
1070
1079
  const lines = ['const SLICE_BUNDLE_DEPENDENCIES = {};'];
1071
- Array.from(modules.values()).forEach((module, index) => {
1080
+ modules.forEach((module, index) => {
1072
1081
  const exportVar = `__sliceDepExports${index}`;
1073
1082
  const transformedContent = this.transformDependencyContent(module.content, exportVar, module.name);
1074
1083
  lines.push(`const ${exportVar} = {};`);
@@ -1105,12 +1114,17 @@ export default class BundleGenerator {
1105
1114
  /**
1106
1115
  * Cleans JavaScript code by removing imports/exports and ensuring class is available globally
1107
1116
  */
1108
- cleanJavaScript(code, componentName) {
1117
+ cleanJavaScript(code, componentName, sourceContext = componentName) {
1109
1118
  // Remove export default
1110
1119
  code = code.replace(/export\s+default\s+/g, '');
1111
1120
 
1112
- // Remove imports (components will already be available)
1113
- code = code.replace(/import\s+.*?from\s+['"].*?['"];?\s*/g, '');
1121
+ // Remove only unsupported imports (relative always removed, allowed absolute kept)
1122
+ const stripped = this.stripImports(code, {
1123
+ sourceContext,
1124
+ collectHoistedImports: true
1125
+ });
1126
+ const hoistedImports = stripped.hoistedImports || [];
1127
+ code = stripped.code;
1114
1128
 
1115
1129
  // Guard customElements.define to avoid duplicate registrations
1116
1130
  code = code.replace(
@@ -1144,7 +1158,10 @@ export default class BundleGenerator {
1144
1158
  // Add return statement for bundle evaluation compatibility
1145
1159
  code += `\nreturn ${componentName};`;
1146
1160
 
1147
- return code;
1161
+ return {
1162
+ code,
1163
+ hoistedImports
1164
+ };
1148
1165
  }
1149
1166
 
1150
1167
  /**
@@ -1173,10 +1190,28 @@ export default class BundleGenerator {
1173
1190
  .update(JSON.stringify(integrityPayload))
1174
1191
  .digest('hex')}`;
1175
1192
 
1193
+ const dependencyModules = this.collectDependencyModules(componentsData);
1194
+ const frameworkComponentKeys = Object.keys(componentsData || {});
1195
+ const frameworkClassIdentifiers = frameworkComponentKeys.map((key) => this.toSafeIdentifier(key));
1196
+ const frameworkReservedIdentifiers = new Set([
1197
+ 'SLICE_BUNDLE',
1198
+ 'SLICE_BUNDLE_COMPONENTS',
1199
+ 'SLICE_BUNDLE_DEPENDENCIES',
1200
+ 'SLICE_FRAMEWORK_CLASSES',
1201
+ ...frameworkClassIdentifiers,
1202
+ ...this.getDependencyExportVariableNames(dependencyModules)
1203
+ ]);
1204
+ const rawHoistedImports = Object.values(componentsData || {})
1205
+ .flatMap((component) => component?.hoistedImports || [])
1206
+ .map((statement) => String(statement).trim())
1207
+ .filter(Boolean);
1208
+ this.validateHoistedImportCollisions(rawHoistedImports, frameworkReservedIdentifiers);
1209
+ const hoistedImportBlock = Array.from(new Set(rawHoistedImports)).join('\n');
1210
+
1176
1211
  const dependencyBlock = this.buildDependencyModuleBlock(componentsData);
1177
1212
  const componentBlock = this.buildComponentBundleBlock(componentsData);
1178
1213
 
1179
- return `/**
1214
+ return `${hoistedImportBlock}${hoistedImportBlock ? '\n\n' : ''}/**
1180
1215
  * Slice.js Bundle
1181
1216
  * Type: ${metadata.type}
1182
1217
  * Generated: ${metadata.generated}
@@ -1231,6 +1266,24 @@ if (window.slice && window.slice.controller) {
1231
1266
  return Array.from(modules.values());
1232
1267
  }
1233
1268
 
1269
+ collectDependencyModulesFromComponents(components = []) {
1270
+ const modules = new Map();
1271
+ for (const component of components || []) {
1272
+ const externalDependencies = component.externalDependencies || {};
1273
+ for (const [moduleName, entry] of Object.entries(externalDependencies)) {
1274
+ if (modules.has(moduleName)) continue;
1275
+ const content = typeof entry === 'string' ? entry : entry?.content;
1276
+ if (!content) continue;
1277
+ modules.set(moduleName, { name: moduleName, content });
1278
+ }
1279
+ }
1280
+ return Array.from(modules.values());
1281
+ }
1282
+
1283
+ getDependencyExportVariableNames(dependencyModules = []) {
1284
+ return (dependencyModules || []).map((_, index) => `__sliceDepExports${index}`);
1285
+ }
1286
+
1234
1287
  transformDependencyContent(content, exportVar, moduleName) {
1235
1288
  const baseName = moduleName.split('/').pop().replace(/\.[^.]+$/, '');
1236
1289
  const dataName = baseName ? `${baseName}Data` : null;
@@ -1450,12 +1503,15 @@ if (window.slice && window.slice.controller) {
1450
1503
  const jsPath = path.join(comp.path, `${fileBaseName}.js`);
1451
1504
  const jsContent = fs.readFileSync(jsPath, 'utf-8');
1452
1505
  const dependencyContents = this.buildDependencyContentsSync(jsContent, comp.path);
1506
+ const cleanedJavaScript = this.cleanJavaScript(jsContent, comp.name, jsPath);
1507
+
1453
1508
  componentsData[componentKey] = {
1454
1509
  name: comp.name,
1455
1510
  category: comp.category,
1456
1511
  categoryType: comp.categoryType,
1457
1512
  isFramework: true,
1458
- js: this.cleanJavaScript(jsContent, comp.name),
1513
+ js: cleanedJavaScript.code,
1514
+ hoistedImports: cleanedJavaScript.hoistedImports,
1459
1515
  externalDependencies: dependencyContents,
1460
1516
  componentDependencies: Array.from(comp.dependencies),
1461
1517
  html: fs.existsSync(path.join(comp.path, `${fileBaseName}.html`))
@@ -1511,8 +1567,293 @@ if (window.slice && window.slice.controller) {
1511
1567
  return dependencyContents;
1512
1568
  }
1513
1569
 
1514
- stripImports(code) {
1515
- return code.replace(/import\s+.*?from\s+['"].*?['"];?\s*/g, '');
1570
+ getConfiguredPublicFolders() {
1571
+ const publicFolders = Array.isArray(this.sliceConfig?.publicFolders)
1572
+ ? this.sliceConfig.publicFolders
1573
+ : [];
1574
+
1575
+ return publicFolders
1576
+ .map((folder) => this.normalizePublicFolder(folder))
1577
+ .filter(Boolean);
1578
+ }
1579
+
1580
+ normalizePublicFolder(folder) {
1581
+ if (typeof folder !== 'string') return null;
1582
+ let normalized = folder.trim();
1583
+ if (!normalized) return null;
1584
+
1585
+ if (!normalized.startsWith('/')) {
1586
+ normalized = `/${normalized}`;
1587
+ }
1588
+
1589
+ normalized = normalized.replace(/\\+/g, '/').replace(/\/+/g, '/');
1590
+ if (normalized.length > 1 && normalized.endsWith('/')) {
1591
+ normalized = normalized.slice(0, -1);
1592
+ }
1593
+
1594
+ return normalized;
1595
+ }
1596
+
1597
+ normalizeImportPath(importPath) {
1598
+ if (typeof importPath !== 'string') return '';
1599
+ const cleanPath = importPath.split(/[?#]/)[0];
1600
+ return cleanPath.replace(/\\+/g, '/').replace(/\/+/g, '/');
1601
+ }
1602
+
1603
+ isRelativeImport(importPath) {
1604
+ return importPath.startsWith('./') || importPath.startsWith('../');
1605
+ }
1606
+
1607
+ isAbsoluteImport(importPath) {
1608
+ return importPath.startsWith('/');
1609
+ }
1610
+
1611
+ isImportInPublicFolders(importPath, publicFolders) {
1612
+ const normalizedImport = this.normalizeImportPath(importPath);
1613
+ return publicFolders.some((folder) => normalizedImport === folder || normalizedImport.startsWith(`${folder}/`));
1614
+ }
1615
+
1616
+ classifyImport(importPath, publicFolders) {
1617
+ if (typeof importPath !== 'string' || !importPath) {
1618
+ return { keep: false, warning: 'Warning: Removing bare import: <unknown>' };
1619
+ }
1620
+
1621
+ if (this.isRelativeImport(importPath)) {
1622
+ return { keep: false, warning: null };
1623
+ }
1624
+
1625
+ if (this.isAbsoluteImport(importPath)) {
1626
+ if (this.isImportInPublicFolders(importPath, publicFolders)) {
1627
+ return { keep: true, warning: null };
1628
+ }
1629
+
1630
+ return {
1631
+ keep: false,
1632
+ warning: `Warning: Removing absolute import outside publicFolders: ${importPath}`
1633
+ };
1634
+ }
1635
+
1636
+ return {
1637
+ keep: false,
1638
+ warning: `Warning: Removing bare import: ${importPath}`
1639
+ };
1640
+ }
1641
+
1642
+ buildImportWarningMessage(baseMessage, sourceContext) {
1643
+ if (!sourceContext) return baseMessage;
1644
+ return `${baseMessage} [${sourceContext}]`;
1645
+ }
1646
+
1647
+ extractLocalBindingsFromImportStatement(statement) {
1648
+ const source = String(statement || '').trim();
1649
+ if (!source.startsWith('import ')) return [];
1650
+ if (/^import\s+['"][^'"]+['"]\s*;?$/.test(source)) return [];
1651
+
1652
+ const bindings = [];
1653
+
1654
+ const defaultMatch = source.match(/^import\s+([A-Za-z_$][\w$]*)\s*(,|\s+from\s+)/);
1655
+ if (defaultMatch && defaultMatch[1] !== '*') {
1656
+ bindings.push(defaultMatch[1]);
1657
+ }
1658
+
1659
+ const namespaceMatch = source.match(/,?\s*\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s+['"]/);
1660
+ if (namespaceMatch) {
1661
+ bindings.push(namespaceMatch[1]);
1662
+ }
1663
+
1664
+ const namedMatch = source.match(/\{([\s\S]*?)\}\s*from\s*['"]/);
1665
+ if (namedMatch) {
1666
+ const namedSection = namedMatch[1];
1667
+ for (const part of namedSection.split(',')) {
1668
+ const cleanPart = part.trim();
1669
+ if (!cleanPart) continue;
1670
+ const aliasParts = cleanPart.split(/\s+as\s+/i).map((v) => v.trim()).filter(Boolean);
1671
+ const localName = aliasParts.length > 1 ? aliasParts[1] : aliasParts[0];
1672
+ if (/^[A-Za-z_$][\w$]*$/.test(localName)) {
1673
+ bindings.push(localName);
1674
+ }
1675
+ }
1676
+ }
1677
+
1678
+ return Array.from(new Set(bindings));
1679
+ }
1680
+
1681
+ validateHoistedImportCollisions(importStatements, reservedIdentifiers = new Set()) {
1682
+ const reserved = reservedIdentifiers instanceof Set
1683
+ ? reservedIdentifiers
1684
+ : new Set(reservedIdentifiers || []);
1685
+ const bindingToStatement = new Map();
1686
+
1687
+ for (const statement of importStatements || []) {
1688
+ const normalizedStatement = String(statement || '').trim();
1689
+ if (!normalizedStatement) continue;
1690
+ const localBindings = this.extractLocalBindingsFromImportStatement(normalizedStatement);
1691
+
1692
+ for (const localBinding of localBindings) {
1693
+ if (reserved.has(localBinding)) {
1694
+ throw new Error(`Hoisted import reserved identifier collision: ${localBinding}`);
1695
+ }
1696
+ const previousStatement = bindingToStatement.get(localBinding);
1697
+ if (previousStatement && previousStatement !== normalizedStatement) {
1698
+ throw new Error(`Hoisted import binding collision: ${localBinding}`);
1699
+ }
1700
+ bindingToStatement.set(localBinding, normalizedStatement);
1701
+ }
1702
+ }
1703
+ }
1704
+
1705
+ parseImportsFromCode(code) {
1706
+ const ast = parse(code, {
1707
+ sourceType: 'module',
1708
+ plugins: ['jsx']
1709
+ });
1710
+
1711
+ const importNodes = [];
1712
+ traverse.default(ast, {
1713
+ ImportDeclaration(pathNode) {
1714
+ importNodes.push(pathNode.node);
1715
+ }
1716
+ });
1717
+
1718
+ return importNodes
1719
+ .filter((node) => typeof node.start === 'number' && typeof node.end === 'number')
1720
+ .sort((a, b) => a.start - b.start);
1721
+ }
1722
+
1723
+ parseImportsWithFallbackScanner(code) {
1724
+ const entries = [];
1725
+ const importRegex = /\bimport\b/g;
1726
+ let match = null;
1727
+
1728
+ while ((match = importRegex.exec(code)) !== null) {
1729
+ const start = match.index;
1730
+ const nextChar = code[start + 'import'.length];
1731
+ if (nextChar === '(') {
1732
+ continue;
1733
+ }
1734
+
1735
+ let index = start + 'import'.length;
1736
+ let quote = null;
1737
+ let escaped = false;
1738
+
1739
+ while (index < code.length) {
1740
+ const char = code[index];
1741
+
1742
+ if (quote) {
1743
+ if (escaped) {
1744
+ escaped = false;
1745
+ } else if (char === '\\') {
1746
+ escaped = true;
1747
+ } else if (char === quote) {
1748
+ quote = null;
1749
+ }
1750
+ index += 1;
1751
+ continue;
1752
+ }
1753
+
1754
+ if (char === '\'' || char === '"' || char === '`') {
1755
+ quote = char;
1756
+ index += 1;
1757
+ continue;
1758
+ }
1759
+
1760
+ if (char === ';') {
1761
+ index += 1;
1762
+ break;
1763
+ }
1764
+
1765
+ index += 1;
1766
+ }
1767
+
1768
+ const end = index;
1769
+ const statement = code.slice(start, end);
1770
+ const fromMatch = statement.match(/\bfrom\s+['"]([^'"]+)['"]/);
1771
+ const sideEffectMatch = statement.match(/\bimport\s+['"]([^'"]+)['"]/);
1772
+ const importPath = fromMatch?.[1] || sideEffectMatch?.[1] || null;
1773
+
1774
+ if (!importPath) {
1775
+ continue;
1776
+ }
1777
+
1778
+ entries.push({ start, end, statement, importPath });
1779
+ importRegex.lastIndex = end;
1780
+ }
1781
+
1782
+ return entries;
1783
+ }
1784
+
1785
+ stripImportsWithFallbackRegex(code, publicFolders, sourceContext, collectHoistedImports) {
1786
+ const hoistedImports = [];
1787
+ const importEntries = this.parseImportsWithFallbackScanner(code);
1788
+ if (importEntries.length === 0) {
1789
+ return { code, hoistedImports };
1790
+ }
1791
+
1792
+ let cleanedCode = '';
1793
+ let cursor = 0;
1794
+ for (const entry of importEntries) {
1795
+ const { start, end, statement, importPath } = entry;
1796
+ const classification = this.classifyImport(importPath, publicFolders);
1797
+ cleanedCode += code.slice(cursor, start);
1798
+ if (classification.keep) {
1799
+ if (collectHoistedImports) {
1800
+ hoistedImports.push(statement.trim());
1801
+ } else {
1802
+ cleanedCode += statement;
1803
+ }
1804
+ } else if (classification.warning) {
1805
+ console.warn(this.buildImportWarningMessage(classification.warning, sourceContext));
1806
+ }
1807
+ cursor = end;
1808
+ }
1809
+
1810
+ cleanedCode += code.slice(cursor);
1811
+
1812
+ return { code: cleanedCode, hoistedImports };
1813
+ }
1814
+
1815
+ stripImports(code, options = {}) {
1816
+ const { sourceContext = null, collectHoistedImports = false } = options;
1817
+ const publicFolders = this.getConfiguredPublicFolders();
1818
+ const hoistedImports = [];
1819
+
1820
+ try {
1821
+ const importNodes = this.parseImportsFromCode(code);
1822
+
1823
+ if (importNodes.length === 0) {
1824
+ return collectHoistedImports ? { code, hoistedImports } : code;
1825
+ }
1826
+
1827
+ let cleaned = '';
1828
+ let cursor = 0;
1829
+
1830
+ for (const node of importNodes) {
1831
+ const importPath = node.source?.value;
1832
+ const classification = this.classifyImport(importPath, publicFolders);
1833
+ const statement = code.slice(node.start, node.end);
1834
+
1835
+ cleaned += code.slice(cursor, node.start);
1836
+ if (classification.keep) {
1837
+ if (collectHoistedImports) {
1838
+ hoistedImports.push(statement.trim());
1839
+ } else {
1840
+ cleaned += statement;
1841
+ }
1842
+ } else if (classification.warning) {
1843
+ console.warn(this.buildImportWarningMessage(classification.warning, sourceContext));
1844
+ }
1845
+
1846
+ cursor = node.end;
1847
+ }
1848
+
1849
+ cleaned += code.slice(cursor);
1850
+ return collectHoistedImports
1851
+ ? { code: cleaned, hoistedImports }
1852
+ : cleaned;
1853
+ } catch (error) {
1854
+ const fallback = this.stripImportsWithFallbackRegex(code, publicFolders, sourceContext, collectHoistedImports);
1855
+ return collectHoistedImports ? fallback : fallback.code;
1856
+ }
1516
1857
  }
1517
1858
 
1518
1859
  async loadComponentsMap() {