cc4-embedded-system 3.1.10 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -19
- package/dist/cli.js +171 -0
- package/dist/config.js +90 -0
- package/dist/gui.js +50 -281
- package/dist/gzip-options.js +10 -0
- package/dist/makefsdata.js +152 -32
- package/dist/minify-options.js +38 -0
- package/dist/public/index.html +85 -41
- package/dist/server.js +289 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,31 +1,40 @@
|
|
|
1
1
|
# CC4EmbeddedSystem V3
|
|
2
|
-
-
|
|
2
|
+
- V3.2.1 is a TypeScript/Node.js rewrite of [lwIP makefsdata](https://github.com/m-labs/lwip/tree/master/src/apps/httpd/makefsdata). GUI mode runs on localhost, default port: `3000`.
|
|
3
|
+
- Assets are handled by type: [html-minifier-next](https://github.com/j9t/html-minifier-next) processes HTML, [Lightning CSS](https://lightningcss.dev/) processes CSS, [esbuild](https://esbuild.github.io/) processes JavaScript, and [SVGO](https://svgo.dev/) processes SVG.
|
|
4
|
+
- Optional deterministic gzip storage reduces the Flash payload for text assets. The MCU sends the stored bytes unchanged; a gzip-capable HTTP client, normally a browser, decompresses the response.
|
|
3
5
|
- This tool is deployed on [npm package](https://www.npmjs.com/package/cc4-embedded-system).
|
|
6
|
+
- The last successful source/destination paths and gzip setting are automatically saved as `cc4es_configs.json` in the operating system's local application-data directory. On Windows this is `%LOCALAPPDATA%\\cc4-embedded-system\\cc4es_configs.json`.
|
|
7
|
+
- The config file is created only after the first successful build, and its complete path is printed at that time. Use `cc4es --config-path` to print it later.
|
|
4
8
|
|
|
5
9
|
## Demo
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-

|
|
12
|
+
### Select input directory
|
|
13
|
+

|
|
14
|
+
### Select output file path
|
|
15
|
+

|
|
16
|
+
### Results
|
|
17
|
+

|
|
18
|
+

|
|
15
19
|
|
|
16
20
|
## Structure
|
|
17
21
|
```text
|
|
18
22
|
CC4EmbeddedSystem/
|
|
19
23
|
├── src/
|
|
20
|
-
│ ├── gui.ts
|
|
21
|
-
│
|
|
22
|
-
│
|
|
24
|
+
│ ├── gui.ts // CLI entry point
|
|
25
|
+
│ ├── cli.ts // Command-line parsing and help text
|
|
26
|
+
│ ├── server.ts // Express GUI server
|
|
27
|
+
│ ├── config.ts // Persistent paths and gzip options
|
|
28
|
+
│ ├── gzip-options.ts // Shared gzip defaults and validation
|
|
29
|
+
│ ├── minify-options.ts // Shared HTML minifier options
|
|
30
|
+
│ ├── makefsdata.ts // Asset routing, gzip, and C code generation
|
|
31
|
+
│ └── utils.ts // Package version lookup
|
|
23
32
|
├── public/
|
|
24
33
|
│ └── index.html // Web GUI dashboard
|
|
25
|
-
├──
|
|
26
|
-
│
|
|
27
|
-
|
|
28
|
-
│ └──
|
|
34
|
+
├── test/
|
|
35
|
+
│ └── cc4es.test.mjs // Native Node.js regression tests
|
|
36
|
+
├── Screenshot/
|
|
37
|
+
│ └── ... // GUI and CLI screenshots
|
|
29
38
|
├── node_modules/ // Required submodules during development
|
|
30
39
|
├── dist/ // Compiled JavaScript output (Auto-generated)
|
|
31
40
|
├── package.json // Project configuration & dependencies
|
|
@@ -33,7 +42,108 @@ CC4EmbeddedSystem/
|
|
|
33
42
|
└── tsconfig.json // TypeScript configuration
|
|
34
43
|
```
|
|
35
44
|
|
|
45
|
+
## Build architecture
|
|
46
|
+
|
|
47
|
+
CC4ES never modifies the source directory. For each file it produces one final response body, writes the HTTP header and body into `fsdata.c`, and keeps lwIP's `FS_FILE_FLAGS_HEADER_INCLUDED` model.
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
source file
|
|
51
|
+
-> extension-based optimizer
|
|
52
|
+
.html/.htm html-minifier-next
|
|
53
|
+
.css Lightning CSS
|
|
54
|
+
.js/.mjs esbuild (or raw with --no-minify-js)
|
|
55
|
+
.svg SVGO
|
|
56
|
+
other raw copy
|
|
57
|
+
-> final response body
|
|
58
|
+
-> optional deterministic gzip when it is smaller
|
|
59
|
+
-> HTTP header + fsdata.c byte array
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
HTML-only options such as `--remove-comments` are not applied to external JavaScript. This prevents an external `.js` file from being interpreted as an HTML comment. `--no-minify-js` and `--no-minify-css` keep their respective external assets unchanged, while HTML can still use its own minification options.
|
|
63
|
+
|
|
64
|
+
### Gzip storage
|
|
65
|
+
|
|
66
|
+
Gzip is optional and disabled by default. `--gzip` considers `.html`, `.htm`, `.css`, `.js`, `.mjs`, `.json`, `.svg`, `.xml`, `.txt`, and `.map`; binary and already-compressed resource types remain raw. A candidate is stored as gzip only when its gzip body is strictly smaller than its final raw body.
|
|
67
|
+
|
|
68
|
+
For gzip bodies, CC4ES writes `Content-Encoding: gzip` before the HTTP header terminator. The gzip mtime and OS metadata are normalized so the same input and options produce byte-identical output. A raw fallback has no `Content-Encoding` header.
|
|
69
|
+
|
|
70
|
+
This is a fixed storage representation, not runtime content negotiation: a gzip-enabled `fsdata.c` does not also store a raw duplicate and does not inspect `Accept-Encoding`. Enable gzip only when the target clients support it; use `--no-gzip` for legacy clients. No MCU decompressor, zlib dependency, lwIP change, or application-code change is required.
|
|
71
|
+
|
|
72
|
+
#### Measurement example
|
|
73
|
+
|
|
74
|
+
The following measured an example app build used `--gzip --gzip-level 9`. The exact result depends on the frontend assets, compiler settings, and firmware image format; treat it as an example rather than a universal saving guarantee.
|
|
75
|
+
|
|
76
|
+
| File | Without gzip | gzip level 9 | Reduction |
|
|
77
|
+
|---|---:|---:|---:|
|
|
78
|
+
| `fsdata.c` | 652,029 B | 197,764 B | 454,265 B (69.67%) |
|
|
79
|
+
| `app.elf` | 2,641,144 B | 2,555,120 B | 86,024 B (3.26%) |
|
|
80
|
+
| `app.hex` | 1,001,729 B | 751,917 B | 249,812 B (24.94%) |
|
|
81
|
+
| `app.img` | 356,108 B | 267,292 B | 88,816 B (24.94%) |
|
|
82
|
+
|
|
83
|
+
The flashed memory regions show where the saving occurs:
|
|
84
|
+
|
|
85
|
+
| Region | Without gzip | gzip level 9 | Reduction |
|
|
86
|
+
|---|---:|---:|---:|
|
|
87
|
+
| `text` | 355,420 B | 266,604 B | 88,816 B |
|
|
88
|
+
| `data` | 680 B | 680 B | 0 B |
|
|
89
|
+
| `bss` | 83,280 B | 83,280 B | 0 B |
|
|
90
|
+
|
|
91
|
+
In this build, gzip saved **88,816 bytes** of Flash, approximately **86.73 KiB**. The ELF file includes debug symbols, so its on-disk size decreases by only 3.26%; use the `.img` size or the `text + data` regions when assessing flashed capacity.
|
|
92
|
+
|
|
93
|
+
## Path Configs
|
|
94
|
+
After the first successful build, CC4EmbeddedSystem saves the most recently used source/destination paths and gzip settings in `cc4es_configs.json`.
|
|
95
|
+
|
|
96
|
+
The file is stored in the local application-data directory by default:
|
|
97
|
+
- Windows: `%LOCALAPPDATA%\cc4-embedded-system\cc4es_configs.json`
|
|
98
|
+
- macOS: `~/Library/Application Support/cc4-embedded-system/cc4es_configs.json`
|
|
99
|
+
- Linux: `$XDG_STATE_HOME/cc4-embedded-system/cc4es_configs.json` (or `~/.local/state/...`)
|
|
100
|
+
|
|
101
|
+
Run `cc4es --config-path` to print the exact path on the current computer.
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"schemaVersion": 2,
|
|
106
|
+
"lastBuild": {
|
|
107
|
+
"src": "C:\\YourProjects\\src",
|
|
108
|
+
"dst": "C:\\YourProjects\\dst\\fsdata.c"
|
|
109
|
+
},
|
|
110
|
+
"gzipOptions": {
|
|
111
|
+
"gzip": false,
|
|
112
|
+
"gzipLevel": 9
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
36
117
|
## Commands
|
|
118
|
+
### CLI Help Texts
|
|
119
|
+
```text
|
|
120
|
+
Usage: cc4es [options]
|
|
121
|
+
|
|
122
|
+
Without options, starts the browser GUI.
|
|
123
|
+
|
|
124
|
+
-h, --help Show this help and exit
|
|
125
|
+
-V, --version Show version and exit
|
|
126
|
+
--config-path Print the full config file path and exit
|
|
127
|
+
--headless Build without server or browser
|
|
128
|
+
-s, --src <directory> Source directory (headless)
|
|
129
|
+
-d, --dst <file|directory> Output C file or directory (default: fsdata.c)
|
|
130
|
+
-p, --port <1-65535> GUI server port
|
|
131
|
+
--[no-]optimize-svg Enable or disable SVGO (default: enabled)
|
|
132
|
+
--svgo-multipass Run SVGO optimization repeatedly
|
|
133
|
+
--[no-]gzip Enable or disable gzip resource storage (default: disabled)
|
|
134
|
+
--gzip-level <1-9> gzip compression level (default: 9)
|
|
135
|
+
|
|
136
|
+
HTML compression options:
|
|
137
|
+
--[no-]collapse-whitespace --[no-]remove-comments
|
|
138
|
+
--[no-]minify-js --[no-]minify-css
|
|
139
|
+
--[no-]conditional-comments --[no-]decode-entities
|
|
140
|
+
--[no-]remove-attribute-quotes --[no-]remove-empty-attributes
|
|
141
|
+
--[no-]remove-redundant-attributes --[no-]use-short-doctype
|
|
142
|
+
|
|
143
|
+
Headless mode requires --src or --dst and never reruns the last build with
|
|
144
|
+
no path options. A missing counterpart uses the last saved path; if no
|
|
145
|
+
destination is saved, fsdata.c is created in the current working directory.
|
|
146
|
+
```
|
|
37
147
|
### Normal Use
|
|
38
148
|
- Global installation
|
|
39
149
|
```bash
|
|
@@ -48,19 +158,57 @@ CC4EmbeddedSystem/
|
|
|
48
158
|
```bash
|
|
49
159
|
cc4es --port 3002
|
|
50
160
|
```
|
|
161
|
+
- Headless build (does not start a server or open a browser)
|
|
162
|
+
```bash
|
|
163
|
+
cc4es --headless --src C:\Project\web --dst C:\Project\fsdata.c
|
|
164
|
+
```
|
|
165
|
+
- Headless build with the default output filename. An existing output directory uses `fsdata.c` inside it.
|
|
166
|
+
```bash
|
|
167
|
+
cc4es --headless --src C:\Project\web
|
|
168
|
+
cc4es --headless --src C:\Project\web --dst C:\Project\output
|
|
169
|
+
```
|
|
170
|
+
- Headless safety: a parameterless command stops instead of rerunning the last build
|
|
171
|
+
```bash
|
|
172
|
+
cc4es --headless
|
|
173
|
+
```
|
|
174
|
+
- CLI help and version
|
|
175
|
+
```bash
|
|
176
|
+
cc4es --help
|
|
177
|
+
cc4es --version
|
|
178
|
+
cc4es --config-path
|
|
179
|
+
```
|
|
180
|
+
- Compression flags can be supplied in headless mode. Every flag accepts a `--no-` form.
|
|
181
|
+
```bash
|
|
182
|
+
cc4es --headless --no-minify-js --remove-attribute-quotes --svgo-multipass
|
|
183
|
+
```
|
|
184
|
+
- Gzip is optional and disabled by default. Only text resources that become smaller are stored as gzip; their response header contains `Content-Encoding: gzip`.
|
|
185
|
+
```bash
|
|
186
|
+
cc4es --headless --src C:\Project\web --dst C:\Project\fsdata.c --gzip --gzip-level 9
|
|
187
|
+
```
|
|
188
|
+
A gzip-enabled image stores one fixed gzip representation for eligible resources. It does not keep a raw duplicate or negotiate `Accept-Encoding`; use `--no-gzip` when a consumer requires non-gzip clients. Test it with a browser or `curl --compressed http://DEVICE_IP/app.js`.
|
|
189
|
+
- Files are processed by type: HTML uses html-minifier-next, CSS uses Lightning CSS, JavaScript uses esbuild, and SVG uses SVGO. `--no-minify-js` and `--no-minify-css` preserve their respective external assets.
|
|
51
190
|
- Update
|
|
52
191
|
```bash
|
|
53
192
|
npm install -g cc4-embedded-system@latest
|
|
54
193
|
```
|
|
55
194
|
### Development
|
|
56
195
|
```bash
|
|
57
|
-
#
|
|
196
|
+
# install locked dependencies
|
|
197
|
+
npm ci
|
|
198
|
+
|
|
199
|
+
# run regression tests
|
|
200
|
+
npm test
|
|
201
|
+
|
|
202
|
+
# compile TypeScript and copy public GUI assets to dist/
|
|
58
203
|
npm run build
|
|
59
204
|
|
|
60
205
|
# mimic global installation
|
|
61
206
|
npm link
|
|
62
207
|
cc4es
|
|
63
208
|
|
|
209
|
+
# headless development test
|
|
210
|
+
npm run dev -- --headless --src C:\Project\web --dst C:\Project\fsdata.c
|
|
211
|
+
|
|
64
212
|
# publish
|
|
65
213
|
npm whoami # verify
|
|
66
214
|
npm login
|
|
@@ -68,4 +216,4 @@ npm version patch --no-git-tag-version # if increase version
|
|
|
68
216
|
npm run dev # dev mode for testing
|
|
69
217
|
npm run build # build the newest
|
|
70
218
|
npm publish --access public
|
|
71
|
-
```
|
|
219
|
+
```
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { DEFAULT_HTML_MINIFY_OPTIONS } from './minify-options.js';
|
|
2
|
+
import { DEFAULT_GZIP_LEVEL, isGzipLevel } from './gzip-options.js';
|
|
3
|
+
export class CliUsageError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'CliUsageError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const MINIFY_FLAGS = {
|
|
10
|
+
'--collapse-whitespace': 'collapseWhitespace',
|
|
11
|
+
'--no-collapse-whitespace': 'collapseWhitespace',
|
|
12
|
+
'--remove-comments': 'removeComments',
|
|
13
|
+
'--no-remove-comments': 'removeComments',
|
|
14
|
+
'--minify-js': 'minifyJS',
|
|
15
|
+
'--no-minify-js': 'minifyJS',
|
|
16
|
+
'--minify-css': 'minifyCSS',
|
|
17
|
+
'--no-minify-css': 'minifyCSS',
|
|
18
|
+
'--conditional-comments': 'processConditionalComments',
|
|
19
|
+
'--no-conditional-comments': 'processConditionalComments',
|
|
20
|
+
'--decode-entities': 'decodeEntities',
|
|
21
|
+
'--no-decode-entities': 'decodeEntities',
|
|
22
|
+
'--remove-attribute-quotes': 'removeAttributeQuotes',
|
|
23
|
+
'--no-remove-attribute-quotes': 'removeAttributeQuotes',
|
|
24
|
+
'--remove-empty-attributes': 'removeEmptyAttributes',
|
|
25
|
+
'--no-remove-empty-attributes': 'removeEmptyAttributes',
|
|
26
|
+
'--remove-redundant-attributes': 'removeRedundantAttributes',
|
|
27
|
+
'--no-remove-redundant-attributes': 'removeRedundantAttributes',
|
|
28
|
+
'--use-short-doctype': 'useShortDoctype',
|
|
29
|
+
'--no-use-short-doctype': 'useShortDoctype'
|
|
30
|
+
};
|
|
31
|
+
const getRequiredValue = (args, index, option) => {
|
|
32
|
+
const value = args[index + 1];
|
|
33
|
+
if (!value || value.startsWith('-'))
|
|
34
|
+
throw new CliUsageError(`${option} requires a value.`);
|
|
35
|
+
return value;
|
|
36
|
+
};
|
|
37
|
+
const parsePort = (value) => {
|
|
38
|
+
if (!/^\d+$/.test(value))
|
|
39
|
+
throw new CliUsageError('--port must be an integer from 1 to 65535.');
|
|
40
|
+
const port = Number.parseInt(value, 10);
|
|
41
|
+
if (port < 1 || port > 65535)
|
|
42
|
+
throw new CliUsageError('--port must be an integer from 1 to 65535.');
|
|
43
|
+
return port;
|
|
44
|
+
};
|
|
45
|
+
const parseGzipLevel = (value) => {
|
|
46
|
+
if (!/^\d+$/.test(value))
|
|
47
|
+
throw new CliUsageError('--gzip-level must be an integer from 1 to 9.');
|
|
48
|
+
const gzipLevel = Number.parseInt(value, 10);
|
|
49
|
+
if (!isGzipLevel(gzipLevel))
|
|
50
|
+
throw new CliUsageError('--gzip-level must be an integer from 1 to 9.');
|
|
51
|
+
return gzipLevel;
|
|
52
|
+
};
|
|
53
|
+
export const parseCliArguments = (args) => {
|
|
54
|
+
const parsed = {
|
|
55
|
+
showHelp: false,
|
|
56
|
+
showVersion: false,
|
|
57
|
+
showConfigPath: false,
|
|
58
|
+
headless: false,
|
|
59
|
+
minifyOpts: { ...DEFAULT_HTML_MINIFY_OPTIONS },
|
|
60
|
+
optimizeSvg: true,
|
|
61
|
+
svgoMultipass: false
|
|
62
|
+
};
|
|
63
|
+
for (let index = 0; index < args.length; index++) {
|
|
64
|
+
const argument = args[index];
|
|
65
|
+
if (!argument)
|
|
66
|
+
continue;
|
|
67
|
+
const minifyOption = MINIFY_FLAGS[argument];
|
|
68
|
+
if (minifyOption) {
|
|
69
|
+
parsed.minifyOpts[minifyOption] = !argument.startsWith('--no-');
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
switch (argument) {
|
|
73
|
+
case '-h':
|
|
74
|
+
case '--help':
|
|
75
|
+
parsed.showHelp = true;
|
|
76
|
+
break;
|
|
77
|
+
case '-V':
|
|
78
|
+
case '--version':
|
|
79
|
+
parsed.showVersion = true;
|
|
80
|
+
break;
|
|
81
|
+
case '--config-path':
|
|
82
|
+
parsed.showConfigPath = true;
|
|
83
|
+
break;
|
|
84
|
+
case '--headless':
|
|
85
|
+
parsed.headless = true;
|
|
86
|
+
break;
|
|
87
|
+
case '-s':
|
|
88
|
+
case '--src':
|
|
89
|
+
case '--input':
|
|
90
|
+
parsed.inputPath = getRequiredValue(args, index, argument);
|
|
91
|
+
index++;
|
|
92
|
+
break;
|
|
93
|
+
case '-d':
|
|
94
|
+
case '--dst':
|
|
95
|
+
case '--output':
|
|
96
|
+
parsed.outputPath = getRequiredValue(args, index, argument);
|
|
97
|
+
index++;
|
|
98
|
+
break;
|
|
99
|
+
case '-p':
|
|
100
|
+
case '--port':
|
|
101
|
+
parsed.port = parsePort(getRequiredValue(args, index, argument));
|
|
102
|
+
index++;
|
|
103
|
+
break;
|
|
104
|
+
case '--optimize-svg':
|
|
105
|
+
parsed.optimizeSvg = true;
|
|
106
|
+
break;
|
|
107
|
+
case '--no-optimize-svg':
|
|
108
|
+
parsed.optimizeSvg = false;
|
|
109
|
+
break;
|
|
110
|
+
case '--svgo-multipass':
|
|
111
|
+
parsed.svgoMultipass = true;
|
|
112
|
+
break;
|
|
113
|
+
case '--gzip':
|
|
114
|
+
parsed.gzip = true;
|
|
115
|
+
break;
|
|
116
|
+
case '--no-gzip':
|
|
117
|
+
parsed.gzip = false;
|
|
118
|
+
break;
|
|
119
|
+
case '--gzip-level': {
|
|
120
|
+
const value = args[index + 1];
|
|
121
|
+
if (!value || value.startsWith('--'))
|
|
122
|
+
throw new CliUsageError('--gzip-level requires a value.');
|
|
123
|
+
parsed.gzipLevel = parseGzipLevel(value);
|
|
124
|
+
index++;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
default:
|
|
128
|
+
throw new CliUsageError(`Unknown option: ${argument}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (parsed.headless && parsed.port !== undefined) {
|
|
132
|
+
throw new CliUsageError('--port is only available when starting the GUI.');
|
|
133
|
+
}
|
|
134
|
+
if (parsed.headless && !parsed.inputPath && !parsed.outputPath) {
|
|
135
|
+
throw new CliUsageError('Headless mode requires --src or --dst. It will not automatically rerun the last build.');
|
|
136
|
+
}
|
|
137
|
+
if (!parsed.headless && (parsed.inputPath || parsed.outputPath)) {
|
|
138
|
+
throw new CliUsageError('--src and --dst require --headless.');
|
|
139
|
+
}
|
|
140
|
+
return parsed;
|
|
141
|
+
};
|
|
142
|
+
export const getHelpText = () => {
|
|
143
|
+
return [
|
|
144
|
+
'Usage: cc4es [options]',
|
|
145
|
+
'',
|
|
146
|
+
'Without options, starts the browser GUI.',
|
|
147
|
+
'',
|
|
148
|
+
' -h, --help Show this help and exit',
|
|
149
|
+
' -V, --version Show version and exit',
|
|
150
|
+
' --config-path Print the full config file path and exit',
|
|
151
|
+
' --headless Build without server or browser',
|
|
152
|
+
' -s, --src <directory> Source directory (headless)',
|
|
153
|
+
' -d, --dst <file|directory> Output C file or directory (default: fsdata.c)',
|
|
154
|
+
' -p, --port <1-65535> GUI server port',
|
|
155
|
+
' --[no-]optimize-svg Enable or disable SVGO (default: enabled)',
|
|
156
|
+
' --svgo-multipass Run SVGO optimization repeatedly',
|
|
157
|
+
' --[no-]gzip Enable or disable gzip resource storage (default: disabled)',
|
|
158
|
+
` --gzip-level <1-9> gzip compression level (default: ${DEFAULT_GZIP_LEVEL})`,
|
|
159
|
+
'',
|
|
160
|
+
'HTML compression options:',
|
|
161
|
+
' --[no-]collapse-whitespace --[no-]remove-comments',
|
|
162
|
+
' --[no-]minify-js --[no-]minify-css',
|
|
163
|
+
' --[no-]conditional-comments --[no-]decode-entities',
|
|
164
|
+
' --[no-]remove-attribute-quotes --[no-]remove-empty-attributes',
|
|
165
|
+
' --[no-]remove-redundant-attributes --[no-]use-short-doctype',
|
|
166
|
+
'',
|
|
167
|
+
'Headless mode requires --src or --dst and never reruns the last build with',
|
|
168
|
+
'no path options. A missing counterpart uses the last saved path; if no',
|
|
169
|
+
'destination is saved, fsdata.c is created in the current working directory.'
|
|
170
|
+
].join('\n');
|
|
171
|
+
};
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { isGzipLevel } from './gzip-options.js';
|
|
5
|
+
export const CONFIG_FILE_NAME = 'cc4es_configs.json';
|
|
6
|
+
const getStateDirectory = () => {
|
|
7
|
+
const homeDirectory = os.homedir();
|
|
8
|
+
if (process.platform === 'win32') {
|
|
9
|
+
return process.env.LOCALAPPDATA ?? path.join(homeDirectory, 'AppData', 'Local');
|
|
10
|
+
}
|
|
11
|
+
if (process.platform === 'darwin') {
|
|
12
|
+
return path.join(homeDirectory, 'Library', 'Application Support');
|
|
13
|
+
}
|
|
14
|
+
return process.env.XDG_STATE_HOME ?? path.join(homeDirectory, '.local', 'state');
|
|
15
|
+
};
|
|
16
|
+
export const getConfigFilePath = () => {
|
|
17
|
+
return path.join(getStateDirectory(), 'cc4-embedded-system', CONFIG_FILE_NAME);
|
|
18
|
+
};
|
|
19
|
+
const isRecord = (value) => {
|
|
20
|
+
return typeof value === 'object' && value !== null;
|
|
21
|
+
};
|
|
22
|
+
const getGzipOptions = (value) => {
|
|
23
|
+
if (!isRecord(value))
|
|
24
|
+
return undefined;
|
|
25
|
+
const gzip = value.gzip;
|
|
26
|
+
const gzipLevel = value.gzipLevel;
|
|
27
|
+
if (typeof gzip !== 'boolean' || !isGzipLevel(gzipLevel))
|
|
28
|
+
return undefined;
|
|
29
|
+
return { gzip, gzipLevel };
|
|
30
|
+
};
|
|
31
|
+
const readConfig = () => {
|
|
32
|
+
try {
|
|
33
|
+
const parsedConfig = JSON.parse(fs.readFileSync(getConfigFilePath(), 'utf8'));
|
|
34
|
+
if (!isRecord(parsedConfig))
|
|
35
|
+
return { schemaVersion: 2 };
|
|
36
|
+
const config = { schemaVersion: 2 };
|
|
37
|
+
const gzipOptions = getGzipOptions(parsedConfig.gzipOptions);
|
|
38
|
+
if (gzipOptions)
|
|
39
|
+
config.gzipOptions = gzipOptions;
|
|
40
|
+
if (!isRecord(parsedConfig.lastBuild))
|
|
41
|
+
return config;
|
|
42
|
+
const src = parsedConfig.lastBuild.src;
|
|
43
|
+
const dst = parsedConfig.lastBuild.dst;
|
|
44
|
+
if (typeof src !== 'string' || typeof dst !== 'string' || !src || !dst) {
|
|
45
|
+
return config;
|
|
46
|
+
}
|
|
47
|
+
config.lastBuild = { src, dst };
|
|
48
|
+
return config;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return { schemaVersion: 2 };
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
export const getLastBuildPaths = () => {
|
|
55
|
+
return readConfig().lastBuild;
|
|
56
|
+
};
|
|
57
|
+
export const getLastGzipOptions = () => {
|
|
58
|
+
return readConfig().gzipOptions;
|
|
59
|
+
};
|
|
60
|
+
export const saveLastBuildPaths = (src, dst, gzipOptions) => {
|
|
61
|
+
const configFilePath = getConfigFilePath();
|
|
62
|
+
const temporaryFilePath = `${configFilePath}.${process.pid}.tmp`;
|
|
63
|
+
const configAlreadyExists = fs.existsSync(configFilePath);
|
|
64
|
+
const config = {
|
|
65
|
+
schemaVersion: 2,
|
|
66
|
+
lastBuild: { src, dst }
|
|
67
|
+
};
|
|
68
|
+
if (gzipOptions)
|
|
69
|
+
config.gzipOptions = gzipOptions;
|
|
70
|
+
try {
|
|
71
|
+
fs.mkdirSync(path.dirname(configFilePath), { recursive: true });
|
|
72
|
+
fs.writeFileSync(temporaryFilePath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
73
|
+
fs.renameSync(temporaryFilePath, configFilePath);
|
|
74
|
+
if (!configAlreadyExists)
|
|
75
|
+
console.log(`📝 Created config: ${configFilePath}`);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
80
|
+
console.warn(`⚠️ Could not save ${CONFIG_FILE_NAME}: ${message}`);
|
|
81
|
+
try {
|
|
82
|
+
if (fs.existsSync(temporaryFilePath))
|
|
83
|
+
fs.unlinkSync(temporaryFilePath);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Ignore cleanup errors because the build itself succeeded.
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
};
|