@bytefaceinc/web-lang 0.1.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.
@@ -0,0 +1,199 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ function inferDefaultCompileTargets(workingDirectory) {
7
+ const layoutPath = path.join(workingDirectory, 'layout.web');
8
+ return fs.existsSync(layoutPath) ? ['layout.web'] : [];
9
+ }
10
+
11
+ function resolveTargets(rawTargets, workingDirectory) {
12
+ const files = [];
13
+ const errors = [];
14
+ const seen = new Set();
15
+
16
+ for (const rawTarget of rawTargets) {
17
+ const resolution = resolveTarget(rawTarget, workingDirectory);
18
+
19
+ if (resolution.error) {
20
+ errors.push(resolution.error);
21
+ continue;
22
+ }
23
+
24
+ for (const file of resolution.files) {
25
+ if (seen.has(file)) {
26
+ continue;
27
+ }
28
+
29
+ seen.add(file);
30
+ files.push(file);
31
+ }
32
+ }
33
+
34
+ files.sort((left, right) => left.localeCompare(right));
35
+
36
+ return { files, errors };
37
+ }
38
+
39
+ function resolveTarget(rawTarget, workingDirectory) {
40
+ if (hasGlobMagic(rawTarget)) {
41
+ const matches = expandGlob(rawTarget, workingDirectory);
42
+ return normalizeResolvedMatches(rawTarget, matches);
43
+ }
44
+
45
+ if (rawTarget !== '.' && rawTarget !== '..' && path.extname(rawTarget) === '') {
46
+ const extensionCandidate = path.resolve(workingDirectory, `${rawTarget}.web`);
47
+
48
+ if (isFile(extensionCandidate)) {
49
+ return {
50
+ files: [extensionCandidate],
51
+ };
52
+ }
53
+ }
54
+
55
+ const resolvedPath = path.resolve(workingDirectory, rawTarget);
56
+
57
+ if (!fs.existsSync(resolvedPath)) {
58
+ return {
59
+ error: `No WEB source matched "${rawTarget}".`,
60
+ };
61
+ }
62
+
63
+ return normalizeResolvedMatches(rawTarget, [resolvedPath]);
64
+ }
65
+
66
+ function normalizeResolvedMatches(rawTarget, matches) {
67
+ const files = [];
68
+
69
+ for (const match of matches) {
70
+ if (!fs.existsSync(match)) {
71
+ continue;
72
+ }
73
+
74
+ const stats = fs.statSync(match);
75
+
76
+ if (stats.isDirectory()) {
77
+ files.push(...listWebFilesInDirectory(match));
78
+ continue;
79
+ }
80
+
81
+ if (stats.isFile() && path.extname(match).toLowerCase() === '.web') {
82
+ files.push(path.resolve(match));
83
+ }
84
+ }
85
+
86
+ if (files.length === 0) {
87
+ return {
88
+ error: `No .web files found for "${rawTarget}".`,
89
+ };
90
+ }
91
+
92
+ return {
93
+ files,
94
+ };
95
+ }
96
+
97
+ function listWebFilesInDirectory(directoryPath) {
98
+ return fs
99
+ .readdirSync(directoryPath, { withFileTypes: true })
100
+ .filter((entry) => entry.isFile() && path.extname(entry.name).toLowerCase() === '.web')
101
+ .map((entry) => path.join(directoryPath, entry.name))
102
+ .sort((left, right) => left.localeCompare(right));
103
+ }
104
+
105
+ function expandGlob(pattern, workingDirectory) {
106
+ const absolutePattern = path.resolve(workingDirectory, pattern);
107
+ const root = path.parse(absolutePattern).root;
108
+ const relativePattern = absolutePattern.slice(root.length);
109
+ const segments = relativePattern.split(path.sep).filter(Boolean);
110
+ return walkGlob(root, segments, 0);
111
+ }
112
+
113
+ function walkGlob(currentPath, segments, index) {
114
+ if (index >= segments.length) {
115
+ return [currentPath];
116
+ }
117
+
118
+ const segment = segments[index];
119
+
120
+ if (!hasGlobMagic(segment)) {
121
+ const nextPath = path.join(currentPath, segment);
122
+ return fs.existsSync(nextPath) ? walkGlob(nextPath, segments, index + 1) : [];
123
+ }
124
+
125
+ if (!fs.existsSync(currentPath) || !fs.statSync(currentPath).isDirectory()) {
126
+ return [];
127
+ }
128
+
129
+ const matcher = globSegmentToRegExp(segment);
130
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true });
131
+ const matches = [];
132
+
133
+ for (const entry of entries) {
134
+ if (!segment.startsWith('.') && entry.name.startsWith('.')) {
135
+ continue;
136
+ }
137
+
138
+ if (!matcher.test(entry.name)) {
139
+ continue;
140
+ }
141
+
142
+ matches.push(...walkGlob(path.join(currentPath, entry.name), segments, index + 1));
143
+ }
144
+
145
+ return matches;
146
+ }
147
+
148
+ function globSegmentToRegExp(segment) {
149
+ let pattern = '^';
150
+
151
+ for (let index = 0; index < segment.length; index += 1) {
152
+ const char = segment[index];
153
+
154
+ if (char === '*') {
155
+ pattern += '.*';
156
+ continue;
157
+ }
158
+
159
+ if (char === '?') {
160
+ pattern += '.';
161
+ continue;
162
+ }
163
+
164
+ if (char === '[') {
165
+ const closingIndex = segment.indexOf(']', index + 1);
166
+
167
+ if (closingIndex === -1) {
168
+ pattern += '\\[';
169
+ continue;
170
+ }
171
+
172
+ pattern += segment.slice(index, closingIndex + 1);
173
+ index = closingIndex;
174
+ continue;
175
+ }
176
+
177
+ pattern += escapeRegExp(char);
178
+ }
179
+
180
+ pattern += '$';
181
+ return new RegExp(pattern);
182
+ }
183
+
184
+ function hasGlobMagic(value) {
185
+ return /[*?[]/.test(value);
186
+ }
187
+
188
+ function escapeRegExp(value) {
189
+ return value.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
190
+ }
191
+
192
+ function isFile(filePath) {
193
+ return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
194
+ }
195
+
196
+ module.exports = {
197
+ inferDefaultCompileTargets,
198
+ resolveTargets,
199
+ };
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_WATCH_DEBOUNCE_MS = 180;
4
+ const DEFAULT_WATCH_IDLE_TIMEOUT_MS = 60 * 60 * 1000;
5
+ const DEFAULT_WATCH_POLL_INTERVAL_MS = 250;
6
+ const DEFAULT_WATCH_SCREENSHOT_INTERVAL_MS = 5 * 60 * 1000;
7
+
8
+ function getWatchRuntimeSettings(env = process.env) {
9
+ return {
10
+ debounceMs: readPositiveInteger(env.WEB_LANG_WATCH_DEBOUNCE_MS, DEFAULT_WATCH_DEBOUNCE_MS),
11
+ idleTimeoutMs: readPositiveInteger(env.WEB_LANG_WATCH_IDLE_TIMEOUT_MS, DEFAULT_WATCH_IDLE_TIMEOUT_MS),
12
+ pollIntervalMs: readPositiveInteger(env.WEB_LANG_WATCH_POLL_INTERVAL_MS, DEFAULT_WATCH_POLL_INTERVAL_MS),
13
+ screenshotIntervalMs: readPositiveInteger(env.WEB_LANG_WATCH_SCREENSHOT_INTERVAL_MS, DEFAULT_WATCH_SCREENSHOT_INTERVAL_MS),
14
+ };
15
+ }
16
+
17
+ function readPositiveInteger(rawValue, fallback) {
18
+ if (rawValue === undefined || rawValue === null || rawValue === '') {
19
+ return fallback;
20
+ }
21
+
22
+ const parsedValue = Number(rawValue);
23
+
24
+ if (!Number.isInteger(parsedValue) || parsedValue <= 0) {
25
+ return fallback;
26
+ }
27
+
28
+ return parsedValue;
29
+ }
30
+
31
+ module.exports = {
32
+ DEFAULT_WATCH_DEBOUNCE_MS,
33
+ DEFAULT_WATCH_IDLE_TIMEOUT_MS,
34
+ DEFAULT_WATCH_POLL_INTERVAL_MS,
35
+ DEFAULT_WATCH_SCREENSHOT_INTERVAL_MS,
36
+ getWatchRuntimeSettings,
37
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@bytefaceinc/web-lang",
3
+ "version": "0.1.1",
4
+ "description": "WEB Lang compiler and CLI for authoring .web power docs that compile to HTML, CSS, render screenshots, and support watch mode",
5
+ "license": "MIT",
6
+ "type": "commonjs",
7
+ "main": "./compiler.js",
8
+ "exports": {
9
+ ".": "./compiler.js",
10
+ "./compiler": "./compiler.js"
11
+ },
12
+ "bin": {
13
+ "web": "bin/web.js"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "files": [
19
+ "compiler.js",
20
+ "bin/",
21
+ "lib/",
22
+ "docs/",
23
+ "LICENSE",
24
+ "README.md"
25
+ ],
26
+ "keywords": [
27
+ "web-lang",
28
+ "web",
29
+ "compiler",
30
+ "cli",
31
+ "dsl",
32
+ "html",
33
+ "css",
34
+ "screenshot",
35
+ "watch",
36
+ "puppeteer",
37
+ "static-site",
38
+ "design-system"
39
+ ],
40
+ "engines": {
41
+ "node": ">=18"
42
+ },
43
+ "dependencies": {
44
+ "puppeteer": "^24.40.0"
45
+ },
46
+ "scripts": {
47
+ "test": "node smoke-test.js",
48
+ "pack:check": "npm pack --dry-run"
49
+ }
50
+ }