ngx-form-designer 0.0.23

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.
Files changed (123) hide show
  1. package/README.md +279 -0
  2. package/convert-legacy-rem-to-sass-function.mjs +198 -0
  3. package/fesm2022/ngx-form-designer.mjs +33548 -0
  4. package/fesm2022/ngx-form-designer.mjs.map +1 -0
  5. package/index.d.ts +5 -0
  6. package/lib/data/data-catalog.d.ts +46 -0
  7. package/lib/data/data-provider.d.ts +69 -0
  8. package/lib/data/data-source-client.d.ts +59 -0
  9. package/lib/data/data-source-parsers.d.ts +7 -0
  10. package/lib/data/external-data-source.d.ts +29 -0
  11. package/lib/data/file-upload-client.d.ts +19 -0
  12. package/lib/data/http-data-source-client.d.ts +31 -0
  13. package/lib/data/in-memory-data-catalog.service.d.ts +12 -0
  14. package/lib/data/runtime-field-data-access-registry.service.d.ts +29 -0
  15. package/lib/data/runtime-field-data-access.d.ts +32 -0
  16. package/lib/data/tree-utils.d.ts +28 -0
  17. package/lib/email-renderer/email-renderer.component.d.ts +21 -0
  18. package/lib/form-core/event-api-reference.d.ts +8 -0
  19. package/lib/form-core/form-engine.d.ts +55 -0
  20. package/lib/form-core/form-event-runner.d.ts +28 -0
  21. package/lib/form-core/models.d.ts +358 -0
  22. package/lib/form-core/plugin-metadata.d.ts +10 -0
  23. package/lib/form-core/rule-evaluation.service.d.ts +18 -0
  24. package/lib/form-core/schema-factory.d.ts +11 -0
  25. package/lib/form-core/schema-guard.d.ts +18 -0
  26. package/lib/form-core/schema-utils.d.ts +9 -0
  27. package/lib/form-designer/data-panel/data-panel.component.d.ts +101 -0
  28. package/lib/form-designer/designer-context.service.d.ts +29 -0
  29. package/lib/form-designer/designer-state.service.d.ts +167 -0
  30. package/lib/form-designer/dynamic-properties/dynamic-properties.component.d.ts +92 -0
  31. package/lib/form-designer/events-panel/events-panel.component.d.ts +21 -0
  32. package/lib/form-designer/events-workspace.component.d.ts +125 -0
  33. package/lib/form-designer/field-palette.component.d.ts +99 -0
  34. package/lib/form-designer/form-designer-shell.component.d.ts +81 -0
  35. package/lib/form-designer/form-preview.component.d.ts +36 -0
  36. package/lib/form-designer/form-settings-inspector.component.d.ts +15 -0
  37. package/lib/form-designer/global-data-manager.component.d.ts +32 -0
  38. package/lib/form-designer/inspector-sections/inspector-advanced-section.component.d.ts +17 -0
  39. package/lib/form-designer/inspector-sections/inspector-backgrounds-section.component.d.ts +14 -0
  40. package/lib/form-designer/inspector-sections/inspector-borders-section.component.d.ts +45 -0
  41. package/lib/form-designer/inspector-sections/inspector-effects-section.component.d.ts +22 -0
  42. package/lib/form-designer/inspector-sections/inspector-layout-section.component.d.ts +33 -0
  43. package/lib/form-designer/inspector-sections/inspector-position-section.component.d.ts +28 -0
  44. package/lib/form-designer/inspector-sections/inspector-size-section.component.d.ts +12 -0
  45. package/lib/form-designer/inspector-sections/inspector-spacing-section.component.d.ts +13 -0
  46. package/lib/form-designer/inspector-sections/inspector-typography-section.component.d.ts +31 -0
  47. package/lib/form-designer/json-form-designer.component.d.ts +17 -0
  48. package/lib/form-designer/layer-tree/layer-tree.component.d.ts +24 -0
  49. package/lib/form-designer/layout-canvas.component.d.ts +69 -0
  50. package/lib/form-designer/page-style.d.ts +2 -0
  51. package/lib/form-designer/properties-panel.component.d.ts +65 -0
  52. package/lib/form-designer/rules-editor/query-builder/query-builder.component.d.ts +23 -0
  53. package/lib/form-designer/rules-editor/rules-panel/rules-panel.component.d.ts +15 -0
  54. package/lib/form-designer/services/widget-definition-resolver.service.d.ts +38 -0
  55. package/lib/form-designer/template-library.d.ts +9 -0
  56. package/lib/form-designer/widget-inspector.component.d.ts +30 -0
  57. package/lib/form-renderer/form-viewer/form-viewer-readonly.component.d.ts +56 -0
  58. package/lib/form-renderer/form-viewer/form-viewer.component.d.ts +55 -0
  59. package/lib/form-renderer/json-form-renderer.component.d.ts +98 -0
  60. package/lib/form-renderer/layout-node.component.d.ts +94 -0
  61. package/lib/plugins/core-plugins.d.ts +5 -0
  62. package/lib/plugins/designer-plugin.d.ts +15 -0
  63. package/lib/plugins/plugin-context.d.ts +18 -0
  64. package/lib/plugins/plugin-providers.d.ts +3 -0
  65. package/lib/plugins/section-definition.d.ts +16 -0
  66. package/lib/theme/theme.service.d.ts +15 -0
  67. package/lib/ui/json-schema-editor.component.d.ts +27 -0
  68. package/lib/ui/monaco-editor.component.d.ts +24 -0
  69. package/lib/ui/ui-accordion.component.d.ts +11 -0
  70. package/lib/ui/ui-box-model.component.d.ts +55 -0
  71. package/lib/ui/ui-color-swatch.component.d.ts +12 -0
  72. package/lib/ui/ui-dimension.component.d.ts +21 -0
  73. package/lib/ui/ui-edge-box.component.d.ts +20 -0
  74. package/lib/ui/ui-field-wrapper.component.d.ts +8 -0
  75. package/lib/ui/ui-icon.module.d.ts +7 -0
  76. package/lib/ui/ui-input.component.d.ts +17 -0
  77. package/lib/ui/ui-range-number.component.d.ts +16 -0
  78. package/lib/ui/ui-select-icon.component.d.ts +18 -0
  79. package/lib/ui/ui-tabs.component.d.ts +25 -0
  80. package/lib/website/website-brick-studio.component.d.ts +67 -0
  81. package/lib/website/website-designer-shell.component.d.ts +53 -0
  82. package/lib/website/website-preview-shell.component.d.ts +25 -0
  83. package/lib/website/website-project.models.d.ts +78 -0
  84. package/lib/website/website-project.service.d.ts +50 -0
  85. package/lib/website/website-section-library.d.ts +6 -0
  86. package/lib/widgets/email-widgets/email-button-widget.component.d.ts +15 -0
  87. package/lib/widgets/email-widgets/email-heading-widget.component.d.ts +15 -0
  88. package/lib/widgets/email-widgets/email-text-widget.component.d.ts +13 -0
  89. package/lib/widgets/email-widgets.d.ts +2 -0
  90. package/lib/widgets/field-widgets/checkbox/checkbox-widget.component.d.ts +28 -0
  91. package/lib/widgets/field-widgets/checkbox-group/checkbox-group-widget.component.d.ts +40 -0
  92. package/lib/widgets/field-widgets/file-upload/file-upload-widget.component.d.ts +45 -0
  93. package/lib/widgets/field-widgets/radio/radio-widget.component.d.ts +39 -0
  94. package/lib/widgets/field-widgets/repeatable-group/repeatable-group-widget.component.d.ts +69 -0
  95. package/lib/widgets/field-widgets/rich-text/rich-text-widget.component.d.ts +17 -0
  96. package/lib/widgets/field-widgets/search/search-widget.component.d.ts +56 -0
  97. package/lib/widgets/field-widgets/select/select-widget.component.d.ts +53 -0
  98. package/lib/widgets/field-widgets/text-field/text-field.component.d.ts +39 -0
  99. package/lib/widgets/field-widgets/tree-select/tree-select-widget.component.d.ts +47 -0
  100. package/lib/widgets/page-link-context.d.ts +8 -0
  101. package/lib/widgets/page-widgets/brick-settings.component.d.ts +23 -0
  102. package/lib/widgets/page-widgets/brick-widget.component.d.ts +47 -0
  103. package/lib/widgets/page-widgets/button-link-settings.component.d.ts +23 -0
  104. package/lib/widgets/page-widgets/button-widget.component.d.ts +21 -0
  105. package/lib/widgets/page-widgets/heading-widget.component.d.ts +22 -0
  106. package/lib/widgets/page-widgets/inline-quill-editor.component.d.ts +35 -0
  107. package/lib/widgets/page-widgets/table-inspector.component.d.ts +17 -0
  108. package/lib/widgets/page-widgets/table-widget.component.d.ts +36 -0
  109. package/lib/widgets/page-widgets/text-block-widget.component.d.ts +22 -0
  110. package/lib/widgets/page-widgets.d.ts +2 -0
  111. package/lib/widgets/static-widgets/image/image-widget.component.d.ts +20 -0
  112. package/lib/widgets/style-helpers.d.ts +8 -0
  113. package/lib/widgets/style-properties.d.ts +28 -0
  114. package/lib/widgets/style-sections.d.ts +1 -0
  115. package/lib/widgets/table-widget.d.ts +2 -0
  116. package/lib/widgets/widget-definition.d.ts +81 -0
  117. package/lib/widgets/widget-editor-context.d.ts +8 -0
  118. package/lib/widgets/widget-packs.d.ts +4 -0
  119. package/lib/widgets/widgets.d.ts +2 -0
  120. package/package.json +47 -0
  121. package/public-api.d.ts +73 -0
  122. package/style-test-bug.scss +2562 -0
  123. package/tailwind.preset.js +62 -0
package/README.md ADDED
@@ -0,0 +1,279 @@
1
+ # NgxFormDesigner
2
+
3
+ A visual Form Designer for Angular applications, featuring a drag-and-drop interface, responsive layout management, and an enterprise-grade rules engine.
4
+
5
+ ## Features
6
+ - **Drag & Drop**: Intuitive canvas for arranging widgets.
7
+ - **Responsive Layout**: Row/Column system with breakpoint support.
8
+ - **Rules Engine**: Conditional logic (show/hide, validation, disabling) based on field values.
9
+ - **Event Workspace**: Dedicated Events tab for configuring `change/click/blur` actions (`setValue`, `log`, `api`).
10
+ - **Event Datasource Runtime**: API event actions write to `datasourceId`, and mapped widgets auto-refresh.
11
+ - **Search Widget**: Dedicated `core.form:search` widget with autosuggest list + datasource-backed querying.
12
+ - **JSON Schema**: Export/Import forms as simple JSON.
13
+ - **Modern UI**: Built with Tailwind CSS and lucide-angular icons.
14
+
15
+ ## Documentation
16
+
17
+ Full beginner-friendly docs live in `projects/ngx-form-designer/docs/README.md`:
18
+
19
+ - `projects/ngx-form-designer/docs/GETTING_STARTED.md`
20
+ - `projects/ngx-form-designer/docs/PUBLIC_API.md`
21
+ - `projects/ngx-form-designer/docs/ARCHITECTURE.md`
22
+ - `projects/ngx-form-designer/docs/SCHEMA_REFERENCE.md`
23
+ - `projects/ngx-form-designer/docs/WIDGETS.md`
24
+ - `projects/ngx-form-designer/docs/PLUGINS.md`
25
+ - `projects/ngx-form-designer/docs/DATA_SOURCES.md`
26
+ - `projects/ngx-form-designer/docs/EVENT_DATASOURCE_RUNTIME.md`
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install ngx-form-designer
32
+ ```
33
+
34
+ ## Dependencies
35
+
36
+ This library relies on **Tailwind CSS** and **lucide-angular** for UI styling and iconography.
37
+
38
+ 1. **Tailwind CSS**: The library uses Tailwind utility classes.
39
+
40
+ **Option A: Using the Preset (Recommended)**
41
+ In your `tailwind.config.js` (or similar), add the preset:
42
+
43
+ ```javascript
44
+ module.exports = {
45
+ presets: [
46
+ require('ngx-form-designer/tailwind.preset.js')
47
+ ],
48
+ content: [
49
+ "./src/**/*.{html,ts}",
50
+ "./node_modules/ngx-form-designer/**/*.{html,ts,mjs}"
51
+ ],
52
+ }
53
+ ```
54
+
55
+ **Option B: Manual Configuration**
56
+ If you prefer to manually copy the theme tokens, see `projects/ngx-form-designer/docs/STYLING_AND_THEME.md`.
57
+
58
+ **Design tokens (recommended):** The demo/theme uses Tailwind tokens like `accent-*`, `ink-*`, `slate-*`, `shadow-card`, and `shadow-popover`. If your app doesn’t define them, the UI will still work but can look different. See `projects/ngx-form-designer/docs/STYLING_AND_THEME.md`.
59
+
60
+ 2. **lucide-angular**: Install and configure lucide-angular icons:
61
+
62
+ ```bash
63
+ npm install lucide-angular
64
+ ```
65
+
66
+ In your `app.config.ts`, import and provide the icons you need:
67
+ ```typescript
68
+ import { LucideAngularModule, icons } from 'lucide-angular';
69
+
70
+ export const appConfig: ApplicationConfig = {
71
+ providers: [
72
+ // ... other providers
73
+ importProvidersFrom(LucideAngularModule.pick(icons))
74
+ ]
75
+ };
76
+ ```
77
+
78
+ ## Usage
79
+
80
+ Import `JsonFormDesignerComponent` (standalone) into your component or route.
81
+
82
+ ```typescript
83
+ import { Component } from '@angular/core';
84
+ import { JsonFormDesignerComponent } from 'ngx-form-designer';
85
+
86
+ @Component({
87
+ standalone: true,
88
+ imports: [JsonFormDesignerComponent],
89
+ template: `<app-json-form-designer></app-json-form-designer>`
90
+ })
91
+ export class MyComponent {}
92
+ ```
93
+
94
+ If you want the full scaffolded UI (top bar + template library) with minimal setup, use the shell component:
95
+
96
+ ```typescript
97
+ import { Component } from '@angular/core';
98
+ import { FormDesignerShellComponent, type DesignerEventApiDefinition } from 'ngx-form-designer';
99
+
100
+ @Component({
101
+ standalone: true,
102
+ imports: [FormDesignerShellComponent],
103
+ template: `<app-form-designer-shell [eventApis]="eventApis"></app-form-designer-shell>`
104
+ })
105
+ export class MyDesignerPage {
106
+ eventApis: DesignerEventApiDefinition[] = [];
107
+ }
108
+ ```
109
+
110
+ ## Provider setup (required)
111
+
112
+ Widgets are registered via plugins. The simplest setup is to register the built-in plugin pack:
113
+
114
+ ```ts
115
+ import { ApplicationConfig } from '@angular/core';
116
+ import { CORE_DESIGNER_PLUGINS, provideDesignerPlugins } from 'ngx-form-designer';
117
+
118
+ export const appConfig: ApplicationConfig = {
119
+ providers: [
120
+ ...provideDesignerPlugins(CORE_DESIGNER_PLUGINS),
121
+ ]
122
+ };
123
+ ```
124
+
125
+ Without this, the palette will be empty and the renderer can’t resolve widgets.
126
+
127
+ ## Publishing
128
+
129
+ Build the library:
130
+ ```bash
131
+ ng build ngx-form-designer
132
+ ```
133
+
134
+ Publish from dist:
135
+ ```bash
136
+ cd dist/ngx-form-designer
137
+ npm publish
138
+ ```
139
+ ## Data Sources (API-only)
140
+
141
+ The library uses a "Data Sources over APIs" architecture. Widgets should not store large datasets (CSV/JSON arrays) directly in schema. Instead, they reference a `datasourceId` and delegate data fetching to a registered `DataProvider`.
142
+
143
+ Full guide: `projects/ngx-form-designer/docs/DATA_SOURCES.md`.
144
+
145
+ ### Concepts
146
+
147
+ 1. **Datasource ID**: A stable string identifier (e.g., `countries`, `users`, `products`) representing a dataset.
148
+ 2. **DataProvider Pipeline**: Widgets request data via the `DataProvider` service.
149
+ - `DataProvider` delegates to `DataSourceClient`.
150
+ - `DataSourceClient` is an abstraction you implement (or use the default) to fetch data.
151
+ 3. **Designer & Runtime Consistency**: The Form Designer uses the exact same pipeline to render "Live Previews" of data in the configuration panel.
152
+
153
+ ### Event datasource
154
+
155
+ Event API actions also write into `datasourceId` (event datasource). Any widget mapped to that datasource (select/search/table/text/date/time) refreshes after the event API call completes.
156
+
157
+ ### Backend API Contract
158
+
159
+ If you use the HTTP-based `DataSourceClient`, your backend should implement these endpoints:
160
+
161
+ | Method | Endpoint | Description |
162
+ | :--- | :--- | :--- |
163
+ | `GET` | `/data-sources` | List available data sources (id, label) |
164
+ | `GET` | `/data-sources/{sourceId}` | Get details for a specific source |
165
+ | `GET` | `/data-sources/{sourceId}/columns` | Get column definitions (name, type) |
166
+ | `POST` | `/data-sources/{sourceId}/query` | query rows with filtering/sorting/paging |
167
+
168
+ **Query Payload Example:**
169
+ ```json
170
+ {
171
+ "page": { "limit": 50, "offset": 0 },
172
+ "search": { "term": "foo", "columns": ["name"] },
173
+ "filters": [
174
+ { "column": "category", "op": "eq", "value": "active" }
175
+ ],
176
+ "sort": [ { "column": "name", "dir": "asc" } ]
177
+ }
178
+ ```
179
+
180
+ ### Host App Setup
181
+
182
+ #### 1. In-Memory / Mock Setup
183
+ Use this for rapid prototyping or testing without a real backend.
184
+
185
+ ```typescript
186
+ import { DataCatalog, InMemoryDataCatalogService } from 'ngx-form-designer';
187
+
188
+ // in app.config.ts
189
+ providers: [
190
+ { provide: DataCatalog, useExisting: InMemoryDataCatalogService }
191
+ ]
192
+ ```
193
+
194
+ #### 2. HTTP Backend Setup
195
+ Use this to connect to your real API.
196
+
197
+ ```typescript
198
+ import { provideHttpDataSourceClient } from 'ngx-form-designer';
199
+
200
+ // in app.config.ts
201
+ providers: [
202
+ provideHttpDataSourceClient({
203
+ baseUrl: 'https://api.example.com',
204
+ getHeaders: () => ({ Authorization: 'Bearer ' + myAuthService.getToken() })
205
+ })
206
+ ]
207
+ ```
208
+
209
+ ### Configuration Examples
210
+
211
+ #### Basic Dropdown (Select)
212
+ Fetches data from `countries` source, uses `name` as label and `isoCode` as value.
213
+
214
+ ```json
215
+ {
216
+ "type": "select",
217
+ "dataConfig": {
218
+ "type": "source",
219
+ "datasourceId": "countries",
220
+ "labelKey": "name",
221
+ "valueKey": "isoCode"
222
+ }
223
+ }
224
+ ```
225
+
226
+ #### Search-Enabled Dropdown
227
+ Enables server-side search to handle large datasets.
228
+
229
+ ```json
230
+ {
231
+ "type": "select",
232
+ "dataConfig": {
233
+ "type": "source",
234
+ "datasourceId": "products",
235
+ "labelKey": "productName",
236
+ "valueKey": "id",
237
+ "searchEnabled": true,
238
+ "optionsLimit": 20,
239
+ "searchColumns": ["productName", "sku"]
240
+ }
241
+ }
242
+ ```
243
+
244
+ #### Tree Select (Hierarchical)
245
+ Expects a flat list of nodes where each node has an ID and a Parent ID.
246
+
247
+ ```json
248
+ {
249
+ "type": "tree-select",
250
+ "dataConfig": {
251
+ "type": "source",
252
+ "datasourceId": "departments",
253
+ "labelKey": "title",
254
+ "valueKey": "id",
255
+ "treeIdKey": "id",
256
+ "treeParentKey": "parentId"
257
+ }
258
+ }
259
+ ```
260
+
261
+ #### Data Table
262
+ Paged table view with server-side sorting.
263
+
264
+ ```json
265
+ {
266
+ "type": "table",
267
+ "dataConfig": {
268
+ "type": "source",
269
+ "datasourceId": "orders",
270
+ "pageSize": 10,
271
+ "sort": [{ "column": "createdAt", "dir": "desc" }]
272
+ },
273
+ "columns": [
274
+ { "key": "id", "label": "Order ID" },
275
+ { "key": "total", "label": "Amount" },
276
+ { "key": "status", "label": "Status" }
277
+ ]
278
+ }
279
+ ```
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { promises as fs } from 'node:fs';
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+ import { pathToFileURL } from 'node:url';
7
+
8
+ const DEFAULT_EXTENSIONS = ['.scss'];
9
+ const DEFAULT_EXCLUDES = ['.git', '.angular', '.next', '.nuxt', 'coverage', 'dist', 'node_modules', 'tmp'];
10
+
11
+ function formatPxIntent(value, precision) {
12
+ const rounded = Number(value.toFixed(precision));
13
+ const normalized = Number.isInteger(rounded) ? `${rounded}` : `${rounded}`;
14
+ return normalized.replace(/(\.\d*?[1-9])0+$/u, '$1').replace(/\.0$/u, '');
15
+ }
16
+
17
+ function normalizeUseStatement(useStatement) {
18
+ return useStatement?.trim() ?? '';
19
+ }
20
+
21
+ function withUseStatement(text, useStatement) {
22
+ const normalizedUseStatement = normalizeUseStatement(useStatement);
23
+ if (!normalizedUseStatement || text.includes(normalizedUseStatement)) {
24
+ return text;
25
+ }
26
+
27
+ return `${normalizedUseStatement}\n\n${text}`;
28
+ }
29
+
30
+ export function convertLegacyRemToSassFunctionInText(text, options = {}) {
31
+ const basePx = options.basePx ?? 10;
32
+ const precision = options.precision ?? 6;
33
+
34
+ const convertedText = text.replace(/(-?(?:\d+\.?\d*|\.\d+))rem\b/gu, (match, numericPart) => {
35
+ const originalValue = Number(numericPart);
36
+ if (!Number.isFinite(originalValue) || originalValue === 0) {
37
+ return match;
38
+ }
39
+
40
+ return `rem(${formatPxIntent(originalValue * basePx, precision)})`;
41
+ });
42
+
43
+ if (convertedText === text) {
44
+ return text;
45
+ }
46
+
47
+ return withUseStatement(convertedText, options.useStatement);
48
+ }
49
+
50
+ async function collectFiles(targetPath, options, collected = []) {
51
+ const stats = await fs.stat(targetPath);
52
+ if (stats.isFile()) {
53
+ if (options.includeExtensions.includes(path.extname(targetPath).toLowerCase())) {
54
+ collected.push(targetPath);
55
+ }
56
+ return collected;
57
+ }
58
+
59
+ const entries = await fs.readdir(targetPath, { withFileTypes: true });
60
+ for (const entry of entries) {
61
+ if (entry.isDirectory() && options.excludeDirectories.has(entry.name)) {
62
+ continue;
63
+ }
64
+
65
+ await collectFiles(path.join(targetPath, entry.name), options, collected);
66
+ }
67
+
68
+ return collected;
69
+ }
70
+
71
+ export async function processScssTargetPath(targetPath, options = {}) {
72
+ const resolvedOptions = {
73
+ basePx: options.basePx ?? 10,
74
+ precision: options.precision ?? 6,
75
+ useStatement: normalizeUseStatement(options.useStatement),
76
+ write: options.write ?? false,
77
+ includeExtensions: options.includeExtensions ?? DEFAULT_EXTENSIONS,
78
+ excludeDirectories: new Set(options.excludeDirectories ?? DEFAULT_EXCLUDES)
79
+ };
80
+
81
+ const absoluteTargetPath = path.resolve(targetPath);
82
+ const files = await collectFiles(absoluteTargetPath, resolvedOptions);
83
+ const changedFiles = [];
84
+
85
+ for (const filePath of files) {
86
+ const currentText = await fs.readFile(filePath, 'utf8');
87
+ const nextText = convertLegacyRemToSassFunctionInText(currentText, resolvedOptions);
88
+ if (nextText === currentText) {
89
+ continue;
90
+ }
91
+
92
+ const replacements = [...currentText.matchAll(/(-?(?:\d+\.?\d*|\.\d+))rem\b/gu)]
93
+ .filter(([, numericPart]) => Number(numericPart) !== 0)
94
+ .length;
95
+
96
+ changedFiles.push({ filePath, replacements, nextText });
97
+
98
+ if (resolvedOptions.write) {
99
+ await fs.writeFile(filePath, nextText, 'utf8');
100
+ }
101
+ }
102
+
103
+ return {
104
+ targetPath: absoluteTargetPath,
105
+ changedFiles,
106
+ visitedFiles: files.length
107
+ };
108
+ }
109
+
110
+ function printUsage() {
111
+ console.log(`Usage:
112
+ node scripts/convert-legacy-rem-to-sass-function.mjs <target-path> [--write]
113
+ [--base=10] [--precision=6] [--use=\"@use 'styles/units' as *;\"]
114
+ [--exclude=node_modules,dist,.git]
115
+
116
+ Only .scss files are scanned. Dry-run is the default.`);
117
+ }
118
+
119
+ function parseCliArgs(argv) {
120
+ const [targetPath, ...flags] = argv;
121
+ if (!targetPath || targetPath === '--help' || targetPath === '-h') {
122
+ return { showHelp: true };
123
+ }
124
+
125
+ const options = {
126
+ write: false,
127
+ basePx: 10,
128
+ precision: 6,
129
+ useStatement: '',
130
+ excludeDirectories: DEFAULT_EXCLUDES
131
+ };
132
+
133
+ for (const flag of flags) {
134
+ if (flag === '--write') {
135
+ options.write = true;
136
+ continue;
137
+ }
138
+ if (flag.startsWith('--base=')) {
139
+ options.basePx = Number(flag.slice('--base='.length));
140
+ continue;
141
+ }
142
+ if (flag.startsWith('--precision=')) {
143
+ options.precision = Number(flag.slice('--precision='.length));
144
+ continue;
145
+ }
146
+ if (flag.startsWith('--use=')) {
147
+ options.useStatement = flag.slice('--use='.length);
148
+ continue;
149
+ }
150
+ if (flag.startsWith('--exclude=')) {
151
+ options.excludeDirectories = flag
152
+ .slice('--exclude='.length)
153
+ .split(',')
154
+ .map(value => value.trim())
155
+ .filter(Boolean);
156
+ continue;
157
+ }
158
+
159
+ throw new Error(`Unknown argument: ${flag}`);
160
+ }
161
+
162
+ if (![options.basePx, options.precision].every(Number.isFinite)) {
163
+ throw new Error('Numeric arguments must be finite numbers.');
164
+ }
165
+
166
+ return { showHelp: false, targetPath, options };
167
+ }
168
+
169
+ async function main() {
170
+ try {
171
+ const parsed = parseCliArgs(process.argv.slice(2));
172
+ if (parsed.showHelp) {
173
+ printUsage();
174
+ return;
175
+ }
176
+
177
+ const result = await processScssTargetPath(parsed.targetPath, parsed.options);
178
+ if (!result.changedFiles.length) {
179
+ console.log(`No legacy rem values needed conversion in ${result.targetPath}`);
180
+ return;
181
+ }
182
+
183
+ for (const changedFile of result.changedFiles) {
184
+ console.log(`${parsed.options.write ? 'updated' : 'would update'} ${changedFile.filePath} (${changedFile.replacements} replacements)`);
185
+ }
186
+
187
+ console.log(
188
+ `${parsed.options.write ? 'Updated' : 'Found'} ${result.changedFiles.length} file(s) out of ${result.visitedFiles} scanned in ${result.targetPath}`
189
+ );
190
+ } catch (error) {
191
+ console.error(error instanceof Error ? error.message : error);
192
+ process.exitCode = 1;
193
+ }
194
+ }
195
+
196
+ if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
197
+ await main();
198
+ }