scb-wc 0.1.18 → 0.1.19

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
@@ -1,6 +1,9 @@
1
- # Om SCB Web Components
1
+ # Om SCB Web Components Preview
2
2
 
3
- SCB Web Components finns för att underlätta skapandet av enhetliga, tillgängliga och användbara webbapplikationer.
3
+ > ⚠️ **Previewkanal**
4
+ > Installera `scb-wc` via `@next` för komponenter under utveckling. API, utseende och beteende kan ändras eller tas bort mellan versioner.
5
+
6
+ SCB Web Components(test) finns för att underlätta skapandet av enhetliga, tillgängliga och användbara webbapplikationer.
4
7
 
5
8
  Komponenterna bygger på:
6
9
  - [Lit](https://lit.dev/)
@@ -8,45 +11,32 @@ Komponenterna bygger på:
8
11
 
9
12
  ## Storybook och dokumentation (internt)
10
13
 
11
- Produktionskomponenterna dokumenteras och demonstreras i Storybook på SCB:s interna adress:
14
+ Previewkomponenterna dokumenteras och demonstreras i Storybook på SCB:s interna testadress:
12
15
 
13
- - Prod: https://webcomponents.scb.intra
16
+ - Test: <https://webcomponentstest.scb.intra>
14
17
 
15
- Testkomponenterna finns på:
18
+ Den publika/stabila Storybooken finns på:
16
19
 
17
- - Test: https://webcomponentstest.scb.intra
20
+ - Prod: <https://webcomponents.scb.intra>
18
21
 
19
22
  ---
20
23
 
21
24
  ## Kom igång
22
25
 
23
- Rekommenderad startväg:
24
-
25
- ```sh
26
- npx scb-wc init html my-app
27
- npx scb-wc init react my-react-app
28
- npx scb-wc init blazor my-blazor-app
29
- ```
30
-
31
- Det skapar en liten starterapp med:
32
- - `scb-header`
33
- - `scb-footer`
34
- - `scb-grid`
35
- - några vanliga komponenter
36
-
37
- Välj sedan den starter som ligger närmast din app och kör `npm install` i den nya mappen.
26
+ > **Obs:** Kör alla kommandon i mappen där din `package.json` ligger.
27
+ > Har du ingen? Kör `npm init -y` i projektroten (eller annan lämplig mapp).
38
28
 
39
- Om du redan har en app med `package.json`, kör kommandon i den mappen och installera paketet:
29
+ Installera previewkanalen:
40
30
 
41
31
  ```sh
42
- npm install scb-wc
32
+ npm install scb-wc@next
43
33
  ```
44
34
 
45
35
  ---
46
36
 
47
37
  ## Alternativ 1: Använd som ES‑moduler (t.ex. i React, Vue, SPA)
48
38
 
49
- Importera CSS och de komponenter du använder (bäst för tree‑shaking):
39
+ Importera CSS och de testkomponenter du använder (bäst för tree‑shaking):
50
40
 
51
41
  ```js
52
42
  // Global CSS + tokens (måste bara importeras en gång)
@@ -82,7 +72,7 @@ Då används paketets egna fontfiler under `node_modules/scb-wc/fonts/`.
82
72
 
83
73
  ## Alternativ 2: Använd i MVC/MPA via `<script type="module">`
84
74
 
85
- Det här läget använder den färdig‑splittrade **MVC‑ESM**‑builden som följer med paketet under:
75
+ Det här läget använder den färdig‑splittade **MVC‑ESM**‑builden som följer med paketet under:
86
76
 
87
77
  ```text
88
78
  node_modules/scb-wc/mvc/
@@ -95,7 +85,7 @@ Lägg till i din apps `package.json`:
95
85
  ```jsonc
96
86
  {
97
87
  "scripts": {
98
- "ui:install": "node -e \"const fs=require('fs'),p=require('path');const src=p.resolve('node_modules/scb-wc/mvc');if(!fs.existsSync(src)){console.error('Hittar inte '+src+'. Har du kört npm install scb-wc?');process.exit(1);}const start=process.env.INIT_CWD||process.cwd();const ov=process.env.npm_config_ui_wwwroot;function findBase(){if(ov){return p.isAbsolute(ov)?ov:p.resolve(start,ov);}let d=start;while(true){const cand=p.join(d,'wwwroot');if(fs.existsSync(cand)) return cand;const up=p.dirname(d);if(up===d) return p.resolve(start,'wwwroot');d=up;}}const base=findBase();const dst=p.resolve(base,'ui');fs.rmSync(dst,{recursive:true,force:true});fs.mkdirSync(base,{recursive:true});fs.cpSync(src,dst,{recursive:true});console.log('Kopierade '+src+' → '+dst);\""
88
+ "ui:install": "node -e \"const fs=require('fs'),p=require('path');const src=p.resolve('node_modules/scb-wc/mvc');if(!fs.existsSync(src)){console.error('Hittar inte '+src+'. Har du kört npm install scb-wc@next?');process.exit(1);}const start=process.env.INIT_CWD||process.cwd();const ov=process.env.npm_config_ui_wwwroot;function findBase(){if(ov){return p.isAbsolute(ov)?ov:p.resolve(start,ov);}let d=start;while(true){const cand=p.join(d,'wwwroot');if(fs.existsSync(cand)) return cand;const up=p.dirname(d);if(up===d) return p.resolve(start,'wwwroot');d=up;}}const base=findBase();const dst=p.resolve(base,'ui');fs.rmSync(dst,{recursive:true,force:true});fs.mkdirSync(base,{recursive:true});fs.cpSync(src,dst,{recursive:true});console.log('Kopierade '+src+' → '+dst);\""
99
89
  }
100
90
  }
101
91
  ```
@@ -142,7 +132,7 @@ Behöver du en annan webbrotsökväg (t.ex. om din `wwwroot` ligger någon annan
142
132
 
143
133
  ## Alternativ 3: Bundlad version (IIFE) för äldre miljöer
144
134
 
145
- Om ESM inte stöds kan du använda den bundlade varianten från paketroten.
135
+ Om ESM inte stöds kan du använda den bundlade test‑varianten från paketroten.
146
136
  Flytta följande två filer från `node_modules/scb-wc` och använd dem i applikationen:
147
137
 
148
138
  ```text
@@ -164,7 +154,7 @@ node_modules/scb-wc/scb-wc.css
164
154
 
165
155
  SCB Web Components fungerar även i Blazor‑appar när du vill använda samma komponenter i både MVC/MPA och Blazor.
166
156
 
167
- Grundprincipen är:
157
+ Grundprincipen är samma som för `scb-wc`:
168
158
 
169
159
  1. Använd MVC‑ESM‑builden (`node_modules/scb-wc/mvc`) och kopiera den till `wwwroot/ui` med `ui:install`.
170
160
  2. Ladda `scb-blazor-bridge.js` från `wwwroot/ui` i din Blazor‑layout.
@@ -177,8 +167,8 @@ Lägg till ett script som kopierar interop-filen från `node_modules` till ditt
177
167
  ```jsonc
178
168
  {
179
169
  "scripts": {
180
- "ui:install": "node -e \"const fs=require('fs'),p=require('path');const src=p.resolve('node_modules/scb-wc/mvc');if(!fs.existsSync(src)){console.error('Hittar inte '+src+'. Har du kört npm install scb-wc?');process.exit(1);}const start=process.env.INIT_CWD||process.cwd();const ov=process.env.npm_config_ui_wwwroot;function findBase(){if(ov){return p.isAbsolute(ov)?ov:p.resolve(start,ov);}let d=start;while(true){const cand=p.join(d,'wwwroot');if(fs.existsSync(cand)) return cand;const up=p.dirname(d);if(up===d) return p.resolve(start,'wwwroot');d=up;}}const base=findBase();const dst=p.resolve(base,'ui');fs.rmSync(dst,{recursive:true,force:true});fs.mkdirSync(base,{recursive:true});fs.cpSync(src,dst,{recursive:true});console.log('Kopierade '+src+' → '+dst);\"",
181
- "ui:blazor:interop": "node -e \"const fs=require('fs'),p=require('path');const start=process.env.INIT_CWD||process.cwd();const src=p.resolve('node_modules/scb-wc/blazor/ScbBlazorInteropBase.cs');if(!fs.existsSync(src)){console.error('Hittar inte '+src+'. Har du kört npm install scb-wc?');process.exit(1);}const dst=p.resolve(start,'ScbBlazor/ScbBlazorInteropBase.cs');fs.mkdirSync(p.dirname(dst),{recursive:true});fs.copyFileSync(src,dst);console.log('Kopierade '+src+' → '+dst);\""
170
+ "ui:install": "node -e \"const fs=require('fs'),p=require('path');const src=p.resolve('node_modules/scb-wc/mvc');if(!fs.existsSync(src)){console.error('Hittar inte '+src+'. Har du kört npm install scb-wc@next?');process.exit(1);}const start=process.env.INIT_CWD||process.cwd();const ov=process.env.npm_config_ui_wwwroot;function findBase(){if(ov){return p.isAbsolute(ov)?ov:p.resolve(start,ov);}let d=start;while(true){const cand=p.join(d,'wwwroot');if(fs.existsSync(cand)) return cand;const up=p.dirname(d);if(up===d) return p.resolve(start,'wwwroot');d=up;}}const base=findBase();const dst=p.resolve(base,'ui');fs.rmSync(dst,{recursive:true,force:true});fs.mkdirSync(base,{recursive:true});fs.cpSync(src,dst,{recursive:true});console.log('Kopierade '+src+' → '+dst);\"",
171
+ "ui:blazor:interop": "node -e \"const fs=require('fs'),p=require('path');const start=process.env.INIT_CWD||process.cwd();const src=p.resolve('node_modules/scb-wc/blazor/ScbBlazorInteropBase.cs');if(!fs.existsSync(src)){console.error('Hittar inte '+src+'. Har du kört npm install scb-wc@next?');process.exit(1);}const dst=p.resolve(start,'ScbBlazor/ScbBlazorInteropBase.cs');fs.mkdirSync(p.dirname(dst),{recursive:true});fs.copyFileSync(src,dst);console.log('Kopierade '+src+' → '+dst);\""
182
172
  }
183
173
  }
184
174
  ```
@@ -192,13 +182,13 @@ Exempel på layout:
192
182
  <script type="module" src="~/ui/scb-blazor-bridge.js"></script>
193
183
  ```
194
184
 
195
- `ScbBlazorInteropBase` kan återanvändas mellan projekt som använder både `scb-wc@latest` och `scb-wc@next`.
185
+ `ScbBlazorInteropBase` är samma bas‑klass i preview- och public-kanalen. Du kan därför dela samma C#‑fil mellan projekt som använder `scb-wc@next` och `scb-wc@latest`.
196
186
 
197
187
  ---
198
188
 
199
189
  ## Viktigt
200
190
 
201
- - `scb-wc@latest` är produktionskanalen. Använd `scb-wc@next` för komponenter under utveckling och experiment.
191
+ - `scb-wc@next` är **previewkanalen**. Komponenter kan ändras eller tas bort mellan versioner. Använd `scb-wc@latest` i externa produktionsmiljöer.
202
192
  - **Kör kommandon i mappen med din `package.json`.** Placeringen av `package.json` styr standardmål för `ui:install` (rot → `wwwroot/ui`, `ClientApp/` → använd `--ui_wwwroot=../wwwroot`).
203
193
  - **Blanda inte MVC‑ESM och IIFE på samma sida.** Välj en distributionsform per sida/app.
204
194
  - **Blazor:** Se till att `scb-blazor-bridge.js` laddas efter att komponent‑JS:et finns på sidan, och att `ScbBlazorInteropBase` inte dupliceras i flera namespaces i samma lösning.
package/all.js CHANGED
@@ -1,103 +1,3 @@
1
- import './all.js';
2
- import './blazor/scb-blazor-bridge.js';
3
- import './index.js';
4
- import './mvc/components/all.js';
5
- import './mvc/components/scb-accordion/scb-accordion-item.js';
6
- import './mvc/components/scb-accordion/scb-accordion.js';
7
- import './mvc/components/scb-app-bar/scb-app-bar.js';
8
- import './mvc/components/scb-avatar/scb-avatar.js';
9
- import './mvc/components/scb-badge/scb-badge.js';
10
- import './mvc/components/scb-breadcrumb/scb-breadcrumb-item.js';
11
- import './mvc/components/scb-breadcrumb/scb-breadcrumb.js';
12
- import './mvc/components/scb-button/scb-button.js';
13
- import './mvc/components/scb-calendar/scb-calendar-event.js';
14
- import './mvc/components/scb-calendar/scb-calendar.js';
15
- import './mvc/components/scb-calendar-card/scb-calendar-card.js';
16
- import './mvc/components/scb-card/scb-card.js';
17
- import './mvc/components/scb-checkbox/scb-checkbox-group.js';
18
- import './mvc/components/scb-checkbox/scb-checkbox.js';
19
- import './mvc/components/scb-chip/scb-chip.js';
20
- import './mvc/components/scb-collapse/scb-collapse.js';
21
- import './mvc/components/scb-cookies-consent/scb-cookies-consent.js';
22
- import './mvc/components/scb-dialog/scb-dialog.js';
23
- import './mvc/components/scb-divider/scb-divider.js';
24
- import './mvc/components/scb-drawer/scb-drawer.js';
25
- import './mvc/components/scb-drop-zone/scb-drop-zone.js';
26
- import './mvc/components/scb-dropdown/scb-dropdown.js';
27
- import './mvc/components/scb-fab/scb-fab.js';
28
- import './mvc/components/scb-fact-card/scb-fact-card-content.js';
29
- import './mvc/components/scb-fact-card/scb-fact-card.js';
30
- import './mvc/components/scb-footer/scb-footer-section.js';
31
- import './mvc/components/scb-footer/scb-footer.js';
32
- import './mvc/components/scb-gallery-grid/scb-gallery-grid.js';
33
- import './mvc/components/scb-grid/scb-grid-item.js';
34
- import './mvc/components/scb-grid/scb-grid.js';
35
- import './mvc/components/scb-grid/scb-stack.js';
36
- import './mvc/components/scb-header/scb-header-menu-group.js';
37
- import './mvc/components/scb-header/scb-header-menu-item.js';
38
- import './mvc/components/scb-header/scb-header-tab.js';
39
- import './mvc/components/scb-header/scb-header-utility.js';
40
- import './mvc/components/scb-header/scb-header.js';
41
- import './mvc/components/scb-horizontal-scroller/scb-horizontal-scroller.js';
42
- import './mvc/components/scb-icon-button/scb-icon-button.js';
43
- import './mvc/components/scb-keyfigure-card/scb-keyfigure-card.js';
44
- import './mvc/components/scb-link/scb-link.js';
45
- import './mvc/components/scb-list/scb-list-item.js';
46
- import './mvc/components/scb-list/scb-list.js';
47
- import './mvc/components/scb-menu/scb-menu-item.js';
48
- import './mvc/components/scb-menu/scb-menu-section.js';
49
- import './mvc/components/scb-menu/scb-menu.js';
50
- import './mvc/components/scb-menu/scb-sub-menu.js';
51
- import './mvc/components/scb-nav/scb-nav-item.js';
52
- import './mvc/components/scb-nav/scb-nav.js';
53
- import './mvc/components/scb-notification-card/scb-notification-card.js';
54
- import './mvc/components/scb-options-menu/scb-options-menu-item.js';
55
- import './mvc/components/scb-options-menu/scb-options-menu.js';
56
- import './mvc/components/scb-options-menu/scb-options-sub-menu.js';
57
- import './mvc/components/scb-overlay/scb-overlay.js';
58
- import './mvc/components/scb-pagination/scb-pagination.js';
59
- import './mvc/components/scb-progress-indicator/scb-progress-indicator.js';
60
- import './mvc/components/scb-progress-stepper/scb-progress-step.js';
61
- import './mvc/components/scb-progress-stepper/scb-progress-stepper.js';
62
- import './mvc/components/scb-radio-button/scb-radio-button.js';
63
- import './mvc/components/scb-radio-button/scb-radio-group.js';
64
- import './mvc/components/scb-scrollspy/scb-scrollspy.js';
65
- import './mvc/components/scb-search/scb-search.js';
66
- import './mvc/components/scb-segmented-button/scb-segmented-button.js';
67
- import './mvc/components/scb-segmented-button/scb-segmented-item.js';
68
- import './mvc/components/scb-select/scb-select-option.js';
69
- import './mvc/components/scb-select/scb-select.js';
70
- import './mvc/components/scb-skeleton/scb-skeleton.js';
71
- import './mvc/components/scb-slider/scb-slider.js';
72
- import './mvc/components/scb-snackbar/scb-snackbar.js';
73
- import './mvc/components/scb-status-pill/scb-status-pill.js';
74
- import './mvc/components/scb-stepper/scb-step.js';
75
- import './mvc/components/scb-stepper/scb-stepper.js';
76
- import './mvc/components/scb-switch/scb-switch.js';
77
- import './mvc/components/scb-table/scb-table.js';
78
- import './mvc/components/scb-table-advanced/scb-table-advanced.js';
79
- import './mvc/components/scb-tabs/scb-primary-tab.js';
80
- import './mvc/components/scb-tabs/scb-secondary-tab.js';
81
- import './mvc/components/scb-tabs/scb-tabs.js';
82
- import './mvc/components/scb-textfield/scb-textfield.js';
83
- import './mvc/components/scb-toc/scb-toc-item.js';
84
- import './mvc/components/scb-toc/scb-toc.js';
85
- import './mvc/components/scb-tooltip/scb-tooltip.js';
86
- import './mvc/components/scb-viz/scb-viz-actions-runtime.js';
87
- import './mvc/components/scb-viz/scb-viz-print-runtime.js';
88
- import './mvc/components/scb-viz/scb-viz-series-differentiation-registry.js';
89
- import './mvc/components/scb-viz/scb-viz-series-differentiation-runtime.js';
90
- import './mvc/components/scb-viz/scb-viz-table-runtime.js';
91
- import './mvc/components/scb-viz/scb-viz.js';
92
- import './mvc/scb-blazor-bridge.js';
93
- import './mvc/vendor/assertClassBrand.js';
94
- import './mvc/vendor/classPrivateFieldGet2.js';
95
- import './mvc/vendor/decorate.js';
96
- import './mvc/vendor/preload-helper.js';
97
- import './mvc/vendor/scb-chevron.js';
98
- import './mvc/vendor/vendor-lit.js';
99
- import './mvc/vendor/vendor-material.js';
100
- import './mvc/vendor/vendor.js';
101
1
  import './scb-accordion/scb-accordion-item.js';
102
2
  import './scb-accordion/scb-accordion.js';
103
3
  import './scb-app-bar/scb-app-bar.js';
@@ -106,9 +6,9 @@ import './scb-badge/scb-badge.js';
106
6
  import './scb-breadcrumb/scb-breadcrumb-item.js';
107
7
  import './scb-breadcrumb/scb-breadcrumb.js';
108
8
  import './scb-button/scb-button.js';
9
+ import './scb-calendar-card/scb-calendar-card.js';
109
10
  import './scb-calendar/scb-calendar-event.js';
110
11
  import './scb-calendar/scb-calendar.js';
111
- import './scb-calendar-card/scb-calendar-card.js';
112
12
  import './scb-card/scb-card.js';
113
13
  import './scb-checkbox/scb-checkbox-group.js';
114
14
  import './scb-checkbox/scb-checkbox.js';
@@ -172,8 +72,8 @@ import './scb-status-pill/scb-status-pill.js';
172
72
  import './scb-stepper/scb-step.js';
173
73
  import './scb-stepper/scb-stepper.js';
174
74
  import './scb-switch/scb-switch.js';
175
- import './scb-table/scb-table.js';
176
75
  import './scb-table-advanced/scb-table-advanced.js';
76
+ import './scb-table/scb-table.js';
177
77
  import './scb-tabs/scb-primary-tab.js';
178
78
  import './scb-tabs/scb-secondary-tab.js';
179
79
  import './scb-tabs/scb-tabs.js';
@@ -187,6 +87,3 @@ import './scb-viz/scb-viz-series-differentiation-registry.js';
187
87
  import './scb-viz/scb-viz-series-differentiation-runtime.js';
188
88
  import './scb-viz/scb-viz-table-runtime.js';
189
89
  import './scb-viz/scb-viz.js';
190
- import './starters/html/main.js';
191
- import './starters/react/vite.config.js';
192
- import './_virtual/_@oxc-project_runtime@0.126.0/helpers/decorate.js';
package/bin/scb-wc.mjs ADDED
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const packageRoot = path.resolve(__dirname, '..');
10
+ const packageJson = JSON.parse(
11
+ fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
12
+ );
13
+ const startersRoot = path.join(packageRoot, 'starters');
14
+
15
+ const usage = `
16
+ SCB Web Components
17
+
18
+ Användning:
19
+ npx scb-wc init <html|react|blazor> [mapp]
20
+
21
+ Exempel:
22
+ npx scb-wc init html my-app
23
+ npx scb-wc init react my-react-app
24
+ npx scb-wc init blazor my-blazor-app
25
+ `.trim();
26
+
27
+ const textExtensions = new Set([
28
+ '.css',
29
+ '.cs',
30
+ '.csproj',
31
+ '.html',
32
+ '.js',
33
+ '.json',
34
+ '.jsx',
35
+ '.md',
36
+ '.razor',
37
+ '.txt',
38
+ ]);
39
+
40
+ function fail(message) {
41
+ console.error(`\n${message}\n`);
42
+ process.exit(1);
43
+ }
44
+
45
+ function renameTemplateEntry(name) {
46
+ return name.startsWith('dot-') ? `.${name.slice(4)}` : name;
47
+ }
48
+
49
+ function toPascalCaseSegment(segment) {
50
+ if (!segment) return '';
51
+ return segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase();
52
+ }
53
+
54
+ function sanitizeStarterNamespace(name) {
55
+ const pascalCaseName = name
56
+ .split(/[^A-Za-z0-9]+/)
57
+ .filter(Boolean)
58
+ .map(toPascalCaseSegment)
59
+ .join('');
60
+
61
+ if (!pascalCaseName) return 'ScbStarterApp';
62
+ if (/^[0-9]/.test(pascalCaseName)) return `_${pascalCaseName}`;
63
+ return pascalCaseName;
64
+ }
65
+
66
+ function ensureTargetIsCreatable(targetDir) {
67
+ if (!fs.existsSync(targetDir)) return;
68
+ const entries = fs.readdirSync(targetDir);
69
+ if (entries.length > 0) {
70
+ fail(`Mappen ${targetDir} finns redan och är inte tom.`);
71
+ }
72
+ }
73
+
74
+ function copyTemplateDir(sourceDir, targetDir) {
75
+ fs.mkdirSync(targetDir, { recursive: true });
76
+
77
+ for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
78
+ const from = path.join(sourceDir, entry.name);
79
+ const to = path.join(targetDir, renameTemplateEntry(entry.name));
80
+
81
+ if (entry.isDirectory()) {
82
+ copyTemplateDir(from, to);
83
+ continue;
84
+ }
85
+
86
+ fs.mkdirSync(path.dirname(to), { recursive: true });
87
+ fs.copyFileSync(from, to);
88
+ }
89
+ }
90
+
91
+ function replacePlaceholders(rootDir, replacements) {
92
+ const walk = (currentDir) => {
93
+ for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {
94
+ const fullPath = path.join(currentDir, entry.name);
95
+
96
+ if (entry.isDirectory()) {
97
+ walk(fullPath);
98
+ continue;
99
+ }
100
+
101
+ const ext = path.extname(entry.name);
102
+ if (!textExtensions.has(ext)) continue;
103
+
104
+ let contents = fs.readFileSync(fullPath, 'utf8');
105
+ for (const [placeholder, value] of Object.entries(replacements)) {
106
+ contents = contents.replaceAll(placeholder, value);
107
+ }
108
+ fs.writeFileSync(fullPath, contents, 'utf8');
109
+ }
110
+ };
111
+
112
+ walk(rootDir);
113
+ }
114
+
115
+ const [, , command, template, maybeTargetDir] = process.argv;
116
+
117
+ if (!command || command === '--help' || command === '-h') {
118
+ console.log(usage);
119
+ process.exit(0);
120
+ }
121
+
122
+ if (command !== 'init') {
123
+ fail(`Okänt kommando: ${command}\n\n${usage}`);
124
+ }
125
+
126
+ if (!template || !['html', 'react', 'blazor'].includes(template)) {
127
+ fail(`Välj en starter: html, react eller blazor.\n\n${usage}`);
128
+ }
129
+
130
+ const targetDir = path.resolve(process.cwd(), maybeTargetDir || `${template}-starter`);
131
+ const sourceDir = path.join(startersRoot, template);
132
+
133
+ if (!fs.existsSync(sourceDir)) {
134
+ fail(`Hittar inte startermallen för ${template}.`);
135
+ }
136
+
137
+ ensureTargetIsCreatable(targetDir);
138
+ copyTemplateDir(sourceDir, targetDir);
139
+ replacePlaceholders(targetDir, {
140
+ '__SCB_WC_PACKAGE_NAME__': packageJson.name,
141
+ '__SCB_WC_VERSION__': packageJson.version,
142
+ '__STARTER_NAME__': path.basename(targetDir),
143
+ '__STARTER_NAMESPACE__': sanitizeStarterNamespace(path.basename(targetDir)),
144
+ });
145
+
146
+ console.log(`\nSkapade ${template}-starter i ${targetDir}\n`);
147
+
148
+ if (template === 'blazor') {
149
+ console.log('Nästa steg:');
150
+ console.log(` 1. cd ${targetDir}`);
151
+ console.log(' 2. npm install');
152
+ console.log(' 3. npm run ui:setup');
153
+ console.log(' 4. dotnet run');
154
+ } else {
155
+ console.log('Nästa steg:');
156
+ console.log(` 1. cd ${targetDir}`);
157
+ console.log(' 2. npm install');
158
+ console.log(' 3. npm run dev');
159
+ }