@webstir-io/webstir-frontend 0.1.40 → 0.1.41
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 +124 -60
- package/dist/assets/imageOptimizer.js +10 -15
- package/dist/assets/precompression.js +1 -1
- package/dist/builders/contentBuilder.js +102 -90
- package/dist/builders/cssBuilder.js +25 -19
- package/dist/builders/htmlBuilder.js +57 -42
- package/dist/builders/index.js +1 -1
- package/dist/builders/jsBuilder.js +219 -76
- package/dist/builders/staticAssetsBuilder.js +27 -9
- package/dist/builders/types.d.ts +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +6 -30
- package/dist/config/manifest.js +7 -6
- package/dist/config/paths.js +2 -2
- package/dist/config/schema.d.ts +8 -0
- package/dist/config/schema.js +7 -6
- package/dist/config/setup.js +1 -1
- package/dist/config/workspace.js +11 -9
- package/dist/core/constants.d.ts +1 -1
- package/dist/core/constants.js +5 -5
- package/dist/core/diagnostics.js +1 -1
- package/dist/core/pages.js +4 -4
- package/dist/hooks.js +3 -3
- package/dist/html/criticalCss.js +6 -3
- package/dist/html/htmlSecurity.d.ts +6 -1
- package/dist/html/htmlSecurity.js +28 -14
- package/dist/html/lazyLoad.js +1 -1
- package/dist/html/pageScaffold.js +1 -1
- package/dist/html/resourceHints.js +5 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/inspect.d.ts +2 -0
- package/dist/inspect.js +110 -0
- package/dist/modes/ssg/metadata.js +4 -4
- package/dist/modes/ssg/routing.js +2 -5
- package/dist/modes/ssg/seo.js +5 -5
- package/dist/modes/ssg/views.js +17 -11
- package/dist/operations.js +18 -10
- package/dist/pipeline.d.ts +1 -0
- package/dist/pipeline.js +6 -1
- package/dist/provider.js +28 -24
- package/dist/runtime/boundary.d.ts +28 -0
- package/dist/runtime/boundary.js +247 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/types.d.ts +52 -0
- package/dist/utils/fs.d.ts +11 -10
- package/dist/utils/fs.js +48 -20
- package/dist/utils/glob.d.ts +8 -0
- package/dist/utils/glob.js +21 -0
- package/dist/utils/hash.js +1 -2
- package/dist/utils/pagePaths.js +2 -2
- package/package.json +19 -14
- package/scripts/publish.sh +2 -94
- package/scripts/update-contract.sh +12 -10
- package/src/assets/assetManifest.ts +39 -29
- package/src/assets/imageOptimizer.ts +91 -82
- package/src/assets/precompression.ts +22 -16
- package/src/builders/contentBuilder.ts +1224 -1149
- package/src/builders/cssBuilder.ts +466 -417
- package/src/builders/htmlBuilder.ts +511 -448
- package/src/builders/index.ts +7 -7
- package/src/builders/jsBuilder.ts +538 -280
- package/src/builders/staticAssetsBuilder.ts +166 -135
- package/src/builders/types.ts +7 -6
- package/src/cli.ts +66 -90
- package/src/config/manifest.ts +16 -14
- package/src/config/paths.ts +5 -5
- package/src/config/schema.ts +38 -37
- package/src/config/setup.ts +7 -7
- package/src/config/workspace.ts +118 -116
- package/src/config/workspaceManifest.ts +14 -14
- package/src/core/constants.ts +62 -62
- package/src/core/diagnostics.ts +26 -26
- package/src/core/pages.ts +19 -19
- package/src/hooks.ts +128 -118
- package/src/html/criticalCss.ts +84 -77
- package/src/html/htmlSecurity.ts +107 -66
- package/src/html/lazyLoad.ts +22 -19
- package/src/html/pageScaffold.ts +37 -28
- package/src/html/resourceHints.ts +83 -74
- package/src/index.ts +2 -0
- package/src/inspect.ts +158 -0
- package/src/modes/ssg/metadata.ts +53 -51
- package/src/modes/ssg/routing.ts +177 -177
- package/src/modes/ssg/seo.ts +208 -200
- package/src/modes/ssg/validation.ts +31 -25
- package/src/modes/ssg/views.ts +257 -238
- package/src/operations.ts +105 -95
- package/src/pipeline.ts +81 -69
- package/src/provider.ts +184 -176
- package/src/runtime/boundary.ts +325 -0
- package/src/runtime/index.ts +1 -0
- package/src/types.ts +107 -48
- package/src/utils/changedFile.ts +22 -22
- package/src/utils/fs.ts +73 -26
- package/src/utils/glob.ts +38 -0
- package/src/utils/hash.ts +2 -4
- package/src/utils/pagePaths.ts +35 -23
- package/src/utils/pathMatch.ts +26 -23
- package/tests/add-page-defaults.test.js +44 -39
- package/tests/bundlerParity.test.js +252 -0
- package/tests/cli.contract.test.js +13 -0
- package/tests/content-pages.test.js +108 -13
- package/tests/css-app-imports.test.js +22 -11
- package/tests/css-page-imports.test.js +26 -13
- package/tests/diagnostics.test.js +39 -36
- package/tests/features.test.js +48 -43
- package/tests/hooks.test.js +58 -42
- package/tests/htmlSecurity.test.js +66 -0
- package/tests/inspect.test.js +148 -0
- package/tests/provider.integration.test.js +71 -20
- package/tests/runtime.test.js +493 -0
- package/tests/ssg-defaults.test.js +284 -177
- package/tests/ssg-guardrails.test.js +51 -51
- package/tsconfig.json +3 -10
- package/dist/watch/frontendFiles.d.ts +0 -3
- package/dist/watch/frontendFiles.js +0 -25
- package/dist/watch/hotUpdateTracker.d.ts +0 -51
- package/dist/watch/hotUpdateTracker.js +0 -205
- package/dist/watch/pipelineHelpers.d.ts +0 -26
- package/dist/watch/pipelineHelpers.js +0 -177
- package/dist/watch/types.d.ts +0 -27
- package/dist/watch/types.js +0 -1
- package/dist/watch/watchCoordinator.d.ts +0 -36
- package/dist/watch/watchCoordinator.js +0 -551
- package/dist/watch/watchDaemon.d.ts +0 -17
- package/dist/watch/watchDaemon.js +0 -127
- package/dist/watch/watchReporter.d.ts +0 -21
- package/dist/watch/watchReporter.js +0 -64
- package/scripts/smoke.mjs +0 -35
- package/src/watch/frontendFiles.ts +0 -32
- package/src/watch/hotUpdateTracker.ts +0 -285
- package/src/watch/pipelineHelpers.ts +0 -242
- package/src/watch/types.ts +0 -23
- package/src/watch/watchCoordinator.ts +0 -666
- package/src/watch/watchDaemon.ts +0 -144
- package/src/watch/watchReporter.ts +0 -98
|
@@ -7,168 +7,199 @@ import { optimizeImages } from '../assets/imageOptimizer.js';
|
|
|
7
7
|
import { relativePathWithin } from '../utils/pathMatch.js';
|
|
8
8
|
|
|
9
9
|
const IMAGE_EXTENSIONS = [
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
EXTENSIONS.png,
|
|
11
|
+
EXTENSIONS.jpg,
|
|
12
|
+
EXTENSIONS.jpeg,
|
|
13
|
+
EXTENSIONS.gif,
|
|
14
|
+
EXTENSIONS.svg,
|
|
15
|
+
EXTENSIONS.webp,
|
|
16
|
+
EXTENSIONS.ico,
|
|
17
17
|
] as const;
|
|
18
18
|
|
|
19
19
|
const FONT_EXTENSIONS = [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
EXTENSIONS.woff,
|
|
21
|
+
EXTENSIONS.woff2,
|
|
22
|
+
EXTENSIONS.ttf,
|
|
23
|
+
EXTENSIONS.otf,
|
|
24
|
+
EXTENSIONS.eot,
|
|
25
25
|
] as const;
|
|
26
26
|
|
|
27
27
|
const MEDIA_EXTENSIONS = [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
EXTENSIONS.mp3,
|
|
29
|
+
EXTENSIONS.m4a,
|
|
30
|
+
EXTENSIONS.wav,
|
|
31
|
+
EXTENSIONS.ogg,
|
|
32
|
+
EXTENSIONS.mp4,
|
|
33
|
+
EXTENSIONS.webm,
|
|
34
|
+
EXTENSIONS.mov,
|
|
35
35
|
] as const;
|
|
36
36
|
|
|
37
37
|
const ALLOW_ALL_ROBOTS = 'User-agent: *\nAllow: /\n';
|
|
38
38
|
|
|
39
39
|
export function createStaticAssetsBuilder(context: BuilderContext): Builder {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
return {
|
|
41
|
+
name: 'static-assets',
|
|
42
|
+
async build(): Promise<void> {
|
|
43
|
+
await copyStaticAssets(context, false);
|
|
44
|
+
},
|
|
45
|
+
async publish(): Promise<void> {
|
|
46
|
+
await copyStaticAssets(context, true);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
async function copyStaticAssets(context: BuilderContext, isProduction: boolean): Promise<void> {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
const { config } = context;
|
|
53
|
+
if (
|
|
54
|
+
!shouldProcess(context, [
|
|
55
|
+
{ directory: config.paths.src.images, extensions: IMAGE_EXTENSIONS },
|
|
56
|
+
{ directory: config.paths.src.fonts, extensions: FONT_EXTENSIONS },
|
|
57
|
+
{ directory: config.paths.src.media, extensions: MEDIA_EXTENSIONS },
|
|
58
|
+
])
|
|
59
|
+
) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const targets = [
|
|
64
|
+
{
|
|
65
|
+
source: config.paths.src.images,
|
|
66
|
+
build: config.paths.build.frontend,
|
|
67
|
+
dist: config.paths.dist.frontend,
|
|
68
|
+
folder: FOLDERS.images,
|
|
69
|
+
extensions: IMAGE_EXTENSIONS,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
source: config.paths.src.fonts,
|
|
73
|
+
build: config.paths.build.frontend,
|
|
74
|
+
dist: config.paths.dist.frontend,
|
|
75
|
+
folder: FOLDERS.fonts,
|
|
76
|
+
extensions: FONT_EXTENSIONS,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
source: config.paths.src.media,
|
|
80
|
+
build: config.paths.build.frontend,
|
|
81
|
+
dist: config.paths.dist.frontend,
|
|
82
|
+
folder: FOLDERS.media,
|
|
83
|
+
extensions: MEDIA_EXTENSIONS,
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
for (const target of targets) {
|
|
88
|
+
if (!(await pathExists(target.source))) {
|
|
89
|
+
continue;
|
|
59
90
|
}
|
|
60
91
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
92
|
+
const changedRelative = relativePathWithin(context.changedFile, target.source);
|
|
93
|
+
const buildDestination = path.join(target.build, target.folder);
|
|
94
|
+
|
|
95
|
+
if (!context.changedFile || !changedRelative) {
|
|
96
|
+
await emptyDir(buildDestination);
|
|
97
|
+
await copy(target.source, buildDestination);
|
|
98
|
+
|
|
99
|
+
if (isProduction) {
|
|
100
|
+
const distDestination = path.join(target.dist, target.folder);
|
|
101
|
+
if (target.folder === FOLDERS.images) {
|
|
102
|
+
if (config.features.imageOptimization) {
|
|
103
|
+
await optimizeImages(buildDestination, distDestination);
|
|
104
|
+
} else {
|
|
105
|
+
await emptyDir(distDestination);
|
|
106
|
+
await copy(buildDestination, distDestination);
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
await emptyDir(distDestination);
|
|
110
|
+
await copy(buildDestination, distDestination);
|
|
70
111
|
}
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
71
115
|
|
|
72
|
-
|
|
73
|
-
const buildDestination = path.join(target.build, target.folder);
|
|
74
|
-
|
|
75
|
-
if (!context.changedFile || !changedRelative) {
|
|
76
|
-
await emptyDir(buildDestination);
|
|
77
|
-
await copy(target.source, buildDestination);
|
|
78
|
-
|
|
79
|
-
if (isProduction) {
|
|
80
|
-
const distDestination = path.join(target.dist, target.folder);
|
|
81
|
-
if (target.folder === FOLDERS.images) {
|
|
82
|
-
if (config.features.imageOptimization) {
|
|
83
|
-
await optimizeImages(buildDestination, distDestination);
|
|
84
|
-
} else {
|
|
85
|
-
await emptyDir(distDestination);
|
|
86
|
-
await copy(buildDestination, distDestination);
|
|
87
|
-
}
|
|
88
|
-
} else {
|
|
89
|
-
await emptyDir(distDestination);
|
|
90
|
-
await copy(buildDestination, distDestination);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
116
|
+
await copySingleAsset(target.source, buildDestination, changedRelative);
|
|
95
117
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
} else {
|
|
104
|
-
await syncImageWithoutOptimization(buildDestination, distDestination, changedRelative);
|
|
105
|
-
}
|
|
106
|
-
} else {
|
|
107
|
-
const sourcePath = path.join(target.source, changedRelative);
|
|
108
|
-
const destPath = path.join(distDestination, changedRelative);
|
|
109
|
-
if (await pathExists(sourcePath)) {
|
|
110
|
-
await ensureDir(path.dirname(destPath));
|
|
111
|
-
await copy(sourcePath, destPath);
|
|
112
|
-
} else {
|
|
113
|
-
await remove(destPath).catch(() => undefined);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
118
|
+
if (isProduction) {
|
|
119
|
+
const distDestination = path.join(target.dist, target.folder);
|
|
120
|
+
if (target.folder === FOLDERS.images) {
|
|
121
|
+
if (config.features.imageOptimization) {
|
|
122
|
+
await optimizeImages(buildDestination, distDestination, [changedRelative]);
|
|
123
|
+
} else {
|
|
124
|
+
await syncImageWithoutOptimization(buildDestination, distDestination, changedRelative);
|
|
116
125
|
}
|
|
126
|
+
} else {
|
|
127
|
+
const sourcePath = path.join(target.source, changedRelative);
|
|
128
|
+
const destPath = path.join(distDestination, changedRelative);
|
|
129
|
+
if (await pathExists(sourcePath)) {
|
|
130
|
+
await ensureDir(path.dirname(destPath));
|
|
131
|
+
await copy(sourcePath, destPath);
|
|
132
|
+
} else {
|
|
133
|
+
await remove(destPath).catch(() => undefined);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
117
136
|
}
|
|
137
|
+
}
|
|
118
138
|
|
|
119
|
-
|
|
139
|
+
await syncRobotsTxt(config, isProduction);
|
|
120
140
|
}
|
|
121
141
|
|
|
122
|
-
async function copySingleAsset(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
142
|
+
async function copySingleAsset(
|
|
143
|
+
sourceRoot: string,
|
|
144
|
+
buildRoot: string,
|
|
145
|
+
relativePath: string,
|
|
146
|
+
): Promise<void> {
|
|
147
|
+
const sourcePath = path.join(sourceRoot, relativePath);
|
|
148
|
+
const destinationPath = path.join(buildRoot, relativePath);
|
|
149
|
+
|
|
150
|
+
if (await pathExists(sourcePath)) {
|
|
151
|
+
await ensureDir(path.dirname(destinationPath));
|
|
152
|
+
await copy(sourcePath, destinationPath);
|
|
153
|
+
} else {
|
|
154
|
+
await remove(destinationPath).catch(() => undefined);
|
|
155
|
+
}
|
|
132
156
|
}
|
|
133
157
|
|
|
134
|
-
async function syncImageWithoutOptimization(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
158
|
+
async function syncImageWithoutOptimization(
|
|
159
|
+
buildRoot: string,
|
|
160
|
+
distRoot: string,
|
|
161
|
+
relativePath: string,
|
|
162
|
+
): Promise<void> {
|
|
163
|
+
const sourcePath = path.join(buildRoot, relativePath);
|
|
164
|
+
const destinationPath = path.join(distRoot, relativePath);
|
|
165
|
+
|
|
166
|
+
if (await pathExists(sourcePath)) {
|
|
167
|
+
await ensureDir(path.dirname(destinationPath));
|
|
168
|
+
await copy(sourcePath, destinationPath);
|
|
169
|
+
} else {
|
|
170
|
+
await remove(destinationPath).catch(() => undefined);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
await Promise.all([
|
|
174
|
+
remove(`${destinationPath}${EXTENSIONS.webp}`).catch(() => undefined),
|
|
175
|
+
remove(`${destinationPath}${EXTENSIONS.avif}`).catch(() => undefined),
|
|
176
|
+
]);
|
|
149
177
|
}
|
|
150
178
|
|
|
151
|
-
async function syncRobotsTxt(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
179
|
+
async function syncRobotsTxt(
|
|
180
|
+
config: BuilderContext['config'],
|
|
181
|
+
isProduction: boolean,
|
|
182
|
+
): Promise<void> {
|
|
183
|
+
const sourcePath = path.join(config.paths.src.frontend, FILES.robotsTxt);
|
|
184
|
+
const buildPath = path.join(config.paths.build.frontend, FILES.robotsTxt);
|
|
185
|
+
|
|
186
|
+
if (await pathExists(sourcePath)) {
|
|
187
|
+
await ensureDir(path.dirname(buildPath));
|
|
188
|
+
await copy(sourcePath, buildPath);
|
|
189
|
+
|
|
190
|
+
if (isProduction) {
|
|
191
|
+
const distPath = path.join(config.paths.dist.frontend, FILES.robotsTxt);
|
|
192
|
+
await ensureDir(path.dirname(distPath));
|
|
193
|
+
await copy(sourcePath, distPath);
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
await ensureDir(path.dirname(buildPath));
|
|
197
|
+
await writeFile(buildPath, ALLOW_ALL_ROBOTS);
|
|
198
|
+
|
|
199
|
+
if (isProduction) {
|
|
200
|
+
const distPath = path.join(config.paths.dist.frontend, FILES.robotsTxt);
|
|
201
|
+
await ensureDir(path.dirname(distPath));
|
|
202
|
+
await writeFile(distPath, ALLOW_ALL_ROBOTS);
|
|
173
203
|
}
|
|
204
|
+
}
|
|
174
205
|
}
|
package/src/builders/types.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import type { EnableFlags, FrontendConfig } from '../types.js';
|
|
2
2
|
|
|
3
3
|
export interface BuilderContext {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
readonly config: FrontendConfig;
|
|
5
|
+
readonly changedFile?: string;
|
|
6
|
+
readonly enable?: EnableFlags;
|
|
7
|
+
readonly env?: Record<string, string | undefined>;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export interface Builder {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
readonly name: string;
|
|
12
|
+
build(context: BuilderContext): Promise<void>;
|
|
13
|
+
publish(context: BuilderContext): Promise<void>;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export type BuilderFactory = (context: BuilderContext) => Builder;
|
package/src/cli.ts
CHANGED
|
@@ -1,108 +1,84 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { runAddPage, runBuild, runPublish, runRebuild } from './operations.js';
|
|
4
|
-
import { WatchDaemon } from './watch/watchDaemon.js';
|
|
5
4
|
|
|
6
5
|
const program = new Command();
|
|
7
6
|
|
|
8
|
-
program
|
|
9
|
-
.name('webstir-frontend')
|
|
10
|
-
.description('Webstir frontend build orchestrator');
|
|
11
|
-
|
|
12
|
-
program
|
|
13
|
-
.command('build')
|
|
14
|
-
.description('Build frontend assets for development workflows')
|
|
15
|
-
.requiredOption('-w, --workspace <path>', 'Absolute path to the workspace root')
|
|
16
|
-
.option('-c, --changed-file <path>', 'Optional path filter for incremental builds')
|
|
17
|
-
.action(async (cmd) => {
|
|
18
|
-
try {
|
|
19
|
-
await runBuild({
|
|
20
|
-
workspaceRoot: cmd.workspace,
|
|
21
|
-
changedFile: cmd.changedFile ?? undefined
|
|
22
|
-
});
|
|
23
|
-
} catch (error) {
|
|
24
|
-
handleError(error);
|
|
25
|
-
}
|
|
26
|
-
});
|
|
7
|
+
program.name('webstir-frontend').description('Webstir frontend build orchestrator');
|
|
27
8
|
|
|
28
9
|
program
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
10
|
+
.command('build')
|
|
11
|
+
.description('Build frontend assets for development workflows')
|
|
12
|
+
.requiredOption('-w, --workspace <path>', 'Absolute path to the workspace root')
|
|
13
|
+
.option('-c, --changed-file <path>', 'Optional path filter for incremental builds')
|
|
14
|
+
.action(async (cmd) => {
|
|
15
|
+
try {
|
|
16
|
+
await runBuild({
|
|
17
|
+
workspaceRoot: cmd.workspace,
|
|
18
|
+
changedFile: cmd.changedFile ?? undefined,
|
|
19
|
+
});
|
|
20
|
+
} catch (error) {
|
|
21
|
+
handleError(error);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
43
24
|
|
|
44
25
|
program
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
26
|
+
.command('publish')
|
|
27
|
+
.description('Build production assets into the dist directory')
|
|
28
|
+
.requiredOption('-w, --workspace <path>', 'Absolute path to the workspace root')
|
|
29
|
+
.option('-m, --mode <mode>', 'Publish mode: bundle or ssg', 'bundle')
|
|
30
|
+
.action(async (cmd) => {
|
|
31
|
+
try {
|
|
32
|
+
await runPublish({
|
|
33
|
+
workspaceRoot: cmd.workspace,
|
|
34
|
+
publishMode: cmd.mode === 'ssg' ? 'ssg' : 'bundle',
|
|
35
|
+
});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
handleError(error);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
59
40
|
|
|
60
41
|
program
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
});
|
|
42
|
+
.command('rebuild')
|
|
43
|
+
.description('Rebuild frontend assets in response to file changes')
|
|
44
|
+
.requiredOption('-w, --workspace <path>', 'Absolute path to the workspace root')
|
|
45
|
+
.requiredOption('-c, --changed-file <path>', 'Path to the changed file triggering the rebuild')
|
|
46
|
+
.action(async (cmd) => {
|
|
47
|
+
try {
|
|
48
|
+
await runRebuild({
|
|
49
|
+
workspaceRoot: cmd.workspace,
|
|
50
|
+
changedFile: cmd.changedFile ?? undefined,
|
|
51
|
+
});
|
|
52
|
+
} catch (error) {
|
|
53
|
+
handleError(error);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
77
56
|
|
|
78
57
|
program
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
handleError(error);
|
|
96
|
-
}
|
|
97
|
-
});
|
|
58
|
+
.command('add-page <name>')
|
|
59
|
+
.description('Scaffold a new frontend page (HTML/CSS/TS)')
|
|
60
|
+
.requiredOption('-w, --workspace <path>', 'Absolute path to the workspace root')
|
|
61
|
+
.option('-m, --mode <mode>', 'Page mode: standard or ssg (defaults to ssg when webstir.mode=ssg)')
|
|
62
|
+
.action(async (name, cmd) => {
|
|
63
|
+
try {
|
|
64
|
+
const rawMode = typeof cmd.mode === 'string' ? cmd.mode.toLowerCase() : undefined;
|
|
65
|
+
await runAddPage({
|
|
66
|
+
workspaceRoot: cmd.workspace,
|
|
67
|
+
pageName: name,
|
|
68
|
+
ssg: rawMode === 'ssg' ? true : rawMode === 'standard' ? false : undefined,
|
|
69
|
+
});
|
|
70
|
+
} catch (error) {
|
|
71
|
+
handleError(error);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
98
74
|
|
|
99
75
|
program.parseAsync(process.argv).catch(handleError);
|
|
100
76
|
|
|
101
77
|
function handleError(error: unknown): void {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
78
|
+
if (error instanceof Error) {
|
|
79
|
+
console.error(error.message);
|
|
80
|
+
} else {
|
|
81
|
+
console.error('Unknown error', error);
|
|
82
|
+
}
|
|
83
|
+
process.exitCode = 1;
|
|
108
84
|
}
|
package/src/config/manifest.ts
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from 'path';
|
|
1
|
+
import { rename } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
3
4
|
import { frontendConfigSchema, type FrontendConfigInput } from './schema.js';
|
|
5
|
+
import { ensureDir, readFile, writeFile } from '../utils/fs.js';
|
|
4
6
|
|
|
5
7
|
export interface WriteManifestOptions {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
readonly outputPath: string;
|
|
9
|
+
readonly data: FrontendConfigInput;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export async function writeConfigManifest(options: WriteManifestOptions): Promise<void> {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const parsed = frontendConfigSchema.parse(options.data);
|
|
14
|
+
const directory = path.dirname(options.outputPath);
|
|
15
|
+
await ensureDir(directory);
|
|
16
|
+
const serialized = JSON.stringify(parsed, undefined, 2);
|
|
17
|
+
const tempPath = path.join(directory, `.webstir-frontend-${process.pid}-${Date.now()}.tmp`);
|
|
18
|
+
await writeFile(tempPath, serialized);
|
|
19
|
+
await rename(tempPath, options.outputPath);
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export async function readConfigManifest(manifestPath: string): Promise<FrontendConfigInput> {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const json = await readFile(manifestPath);
|
|
24
|
+
const parsed = JSON.parse(json) as unknown;
|
|
25
|
+
return frontendConfigSchema.parse(parsed);
|
|
24
26
|
}
|
package/src/config/paths.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { promises as fs } from 'fs';
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
3
|
import { FOLDERS } from '../core/constants.js';
|
|
4
4
|
|
|
5
5
|
export const FRONTEND_MANIFEST_FILENAME = 'frontend-manifest.json';
|
|
6
6
|
|
|
7
7
|
export function resolveManifestPath(workspaceRoot: string): string {
|
|
8
|
-
|
|
8
|
+
return path.join(workspaceRoot, FOLDERS.webstir, FRONTEND_MANIFEST_FILENAME);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export async function ensureWebstirDirectory(workspaceRoot: string): Promise<void> {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const webstirPath = path.join(workspaceRoot, FOLDERS.webstir);
|
|
13
|
+
await fs.mkdir(webstirPath, { recursive: true });
|
|
14
14
|
}
|