@t09tanaka/stoneage 0.1.0

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 (47) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/LICENSE +21 -0
  3. package/README.md +768 -0
  4. package/dist/agent-skill.d.ts +1 -0
  5. package/dist/agent-skill.js +140 -0
  6. package/dist/assets.d.ts +125 -0
  7. package/dist/assets.js +341 -0
  8. package/dist/cli.d.ts +236 -0
  9. package/dist/cli.js +3077 -0
  10. package/dist/core.d.ts +473 -0
  11. package/dist/core.js +2897 -0
  12. package/dist/data.d.ts +121 -0
  13. package/dist/data.js +358 -0
  14. package/dist/deploy.d.ts +34 -0
  15. package/dist/deploy.js +203 -0
  16. package/dist/dev.d.ts +36 -0
  17. package/dist/dev.js +313 -0
  18. package/dist/example.d.ts +134 -0
  19. package/dist/example.js +1272 -0
  20. package/dist/fragment/client.d.ts +13 -0
  21. package/dist/fragment/client.js +150 -0
  22. package/dist/fragment.d.ts +15 -0
  23. package/dist/fragment.js +27 -0
  24. package/dist/html.d.ts +57 -0
  25. package/dist/html.js +208 -0
  26. package/dist/images.d.ts +95 -0
  27. package/dist/images.js +292 -0
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.js +1 -0
  30. package/dist/migration.d.ts +157 -0
  31. package/dist/migration.js +983 -0
  32. package/dist/optimize.d.ts +15 -0
  33. package/dist/optimize.js +215 -0
  34. package/dist/pagination.d.ts +26 -0
  35. package/dist/pagination.js +62 -0
  36. package/dist/paths.d.ts +1 -0
  37. package/dist/paths.js +10 -0
  38. package/dist/search.d.ts +32 -0
  39. package/dist/search.js +55 -0
  40. package/dist/testing.d.ts +66 -0
  41. package/dist/testing.js +97 -0
  42. package/dist/validate.d.ts +2 -0
  43. package/dist/validate.js +1 -0
  44. package/jsx-loader.mjs +52 -0
  45. package/jsx-register.mjs +7 -0
  46. package/package.json +135 -0
  47. package/tsconfig.base.json +16 -0
package/dist/images.js ADDED
@@ -0,0 +1,292 @@
1
+ import { mkdir, stat } from "node:fs/promises";
2
+ import { basename, dirname, extname, join } from "node:path";
3
+ import { publicAssetOutputPath } from "./paths.js";
4
+ /**
5
+ * Number of distinct source images that could not be optimized because no image
6
+ * converter (sharp) was available. Lets callers surface an actionable warning
7
+ * instead of silently shipping unoptimized images.
8
+ */
9
+ export function countConverterUnavailable(result) {
10
+ const sources = new Set();
11
+ for (const skipped of result.skipped) {
12
+ if (skipped.reason === "converter-unavailable") {
13
+ sources.add(skipped.source);
14
+ }
15
+ }
16
+ return sources.size;
17
+ }
18
+ /**
19
+ * Resolve whether the optional `sharp` dependency can be loaded in the current
20
+ * environment. Returns false when sharp is not installed or its native binary
21
+ * is unavailable for the current platform.
22
+ */
23
+ export async function isSharpAvailable(loadConverter = loadSharpConverter) {
24
+ return Boolean(await loadConverter());
25
+ }
26
+ export async function optimizeImages(options) {
27
+ const skipped = [];
28
+ const widths = [...new Set(options.widths)].sort((left, right) => left - right);
29
+ const variantTasks = [];
30
+ let converter = options.converter;
31
+ let converterLoaded = Boolean(converter);
32
+ async function getConverter() {
33
+ if (!converterLoaded) {
34
+ converterLoaded = true;
35
+ converter = options.loadConverter ? await options.loadConverter() : await loadSharpConverter();
36
+ }
37
+ return converter;
38
+ }
39
+ for (const image of options.images) {
40
+ if (!isResizableInput(image.source)) {
41
+ skipped.push({ source: image.source, reason: "unsupported-format" });
42
+ continue;
43
+ }
44
+ const sourceStats = options.skipExisting ? await stat(image.source) : undefined;
45
+ for (const format of options.formats) {
46
+ for (const width of widths) {
47
+ const publicPath = variantPublicPath(image.publicPath, width, format);
48
+ const outputPath = join(options.outDir, publicAssetOutputPath(publicPath));
49
+ if (sourceStats) {
50
+ const existingStats = await currentExistingVariant(outputPath, sourceStats.mtimeMs);
51
+ if (existingStats) {
52
+ skipped.push({
53
+ source: image.source,
54
+ publicPath,
55
+ width,
56
+ format,
57
+ bytes: existingStats.size,
58
+ reason: "existing-current",
59
+ });
60
+ continue;
61
+ }
62
+ }
63
+ variantTasks.push({
64
+ image,
65
+ publicPath,
66
+ outputPath,
67
+ width,
68
+ format,
69
+ });
70
+ }
71
+ }
72
+ }
73
+ if (variantTasks.length === 0) {
74
+ return {
75
+ generated: [],
76
+ skipped,
77
+ };
78
+ }
79
+ const resolvedConverter = await getConverter();
80
+ if (!resolvedConverter) {
81
+ const skippedSources = new Set();
82
+ for (const task of variantTasks) {
83
+ if (skippedSources.has(task.image.source)) {
84
+ continue;
85
+ }
86
+ skippedSources.add(task.image.source);
87
+ skipped.push({
88
+ source: task.image.source,
89
+ reason: "converter-unavailable",
90
+ });
91
+ }
92
+ return {
93
+ generated: [],
94
+ skipped,
95
+ };
96
+ }
97
+ const generated = await mapConcurrent(variantTasks, 8, async (task) => {
98
+ await mkdir(dirname(task.outputPath), { recursive: true });
99
+ await resolvedConverter({
100
+ source: task.image.source,
101
+ outputPath: task.outputPath,
102
+ width: task.width,
103
+ format: task.format,
104
+ });
105
+ return {
106
+ source: task.image.source,
107
+ publicPath: task.publicPath,
108
+ width: task.width,
109
+ format: task.format,
110
+ bytes: (await stat(task.outputPath)).size,
111
+ };
112
+ });
113
+ return {
114
+ generated,
115
+ skipped,
116
+ };
117
+ }
118
+ async function mapConcurrent(items, concurrency, worker) {
119
+ const limit = Math.max(1, Math.floor(concurrency));
120
+ const results = new Array(items.length);
121
+ let nextIndex = 0;
122
+ async function runWorker() {
123
+ while (nextIndex < items.length) {
124
+ const index = nextIndex;
125
+ nextIndex += 1;
126
+ results[index] = await worker(items[index]);
127
+ }
128
+ }
129
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => runWorker()));
130
+ return results;
131
+ }
132
+ export function imagesForSource(resultOrImages, sourceOrPublicPath) {
133
+ const images = Array.isArray(resultOrImages) ? resultOrImages : resultOrImages.generated;
134
+ return images.filter((image) => image.source === sourceOrPublicPath ||
135
+ image.publicPath === sourceOrPublicPath ||
136
+ image.publicPath === variantPublicPath(sourceOrPublicPath, image.width, image.format));
137
+ }
138
+ export function srcsetFor(images) {
139
+ return images
140
+ .slice()
141
+ .sort((left, right) => left.width - right.width)
142
+ .map((image) => `${image.publicPath} ${image.width}w`)
143
+ .join(", ");
144
+ }
145
+ export function pictureSourcesFor(images, formats = ["avif", "webp", "jpeg", "png"]) {
146
+ return formats
147
+ .map((format) => {
148
+ const srcset = srcsetFor(images.filter((image) => image.format === format));
149
+ return srcset
150
+ ? {
151
+ format,
152
+ type: `image/${format}`,
153
+ srcset,
154
+ }
155
+ : undefined;
156
+ })
157
+ .filter((source) => Boolean(source));
158
+ }
159
+ export function pictureAttrsFor(images, fallback) {
160
+ const fallbackImage = selectFallbackImage(images, fallback.fallbackFormat);
161
+ const sources = pictureSourcesFor(images.filter((image) => image.format !== fallbackImage?.format), fallback.formats).map((source) => withOptionalSizes(source, fallback.sizes));
162
+ return {
163
+ sources,
164
+ img: {
165
+ src: fallbackImage?.publicPath ?? fallback.src,
166
+ ...(fallbackImage
167
+ ? { srcset: srcsetFor(images.filter((image) => image.format === fallbackImage.format)) }
168
+ : {}),
169
+ ...optionalSizes(fallback.sizes),
170
+ width: fallback.width,
171
+ height: fallback.height,
172
+ ...(fallback.loading ? { loading: fallback.loading } : {}),
173
+ ...(fallback.decoding ? { decoding: fallback.decoding } : {}),
174
+ alt: fallback.alt,
175
+ },
176
+ };
177
+ }
178
+ export function pictureHtmlFor(images, fallback) {
179
+ return renderPicture(pictureAttrsFor(images, fallback));
180
+ }
181
+ export function renderPicture(attrs) {
182
+ const sources = attrs.sources
183
+ .map((source) => renderElement("source", [
184
+ ["type", source.type],
185
+ ["srcset", source.srcset],
186
+ ["sizes", source.sizes],
187
+ ]))
188
+ .join("");
189
+ const img = renderElement("img", [
190
+ ["src", attrs.img.src],
191
+ ["srcset", attrs.img.srcset],
192
+ ["sizes", attrs.img.sizes],
193
+ ["width", attrs.img.width],
194
+ ["height", attrs.img.height],
195
+ ["loading", attrs.img.loading],
196
+ ["decoding", attrs.img.decoding],
197
+ ["alt", attrs.img.alt],
198
+ ]);
199
+ return `<picture>${sources}${img}</picture>`;
200
+ }
201
+ export async function loadSharpConverter() {
202
+ // Escape hatch (primarily for tests) to simulate a missing optional sharp
203
+ // dependency without uninstalling it.
204
+ if (process.env.STONEAGE_DISABLE_SHARP) {
205
+ return undefined;
206
+ }
207
+ try {
208
+ const importOptional = new Function("specifier", "return import(specifier)");
209
+ const mod = (await importOptional("sharp"));
210
+ return async ({ source, outputPath, width, format }) => {
211
+ await mod.default(source).resize(width).toFormat(format).toFile(outputPath);
212
+ };
213
+ }
214
+ catch {
215
+ return undefined;
216
+ }
217
+ }
218
+ function variantPublicPath(publicPath, width, format) {
219
+ const cleanPath = publicPath.split(/[?#]/, 1)[0] ?? publicPath;
220
+ const extension = extname(cleanPath);
221
+ const base = cleanPath.slice(0, cleanPath.length - extension.length);
222
+ return `${base}-${width}.${format}`;
223
+ }
224
+ async function currentExistingVariant(outputPath, sourceMtimeMs) {
225
+ try {
226
+ const outputStats = await stat(outputPath);
227
+ return outputStats.mtimeMs >= sourceMtimeMs ? outputStats : undefined;
228
+ }
229
+ catch {
230
+ return undefined;
231
+ }
232
+ }
233
+ function isResizableInput(path) {
234
+ const extension = extname(basename(path)).toLowerCase();
235
+ return extension === ".jpg" || extension === ".jpeg" || extension === ".png" || extension === ".webp";
236
+ }
237
+ function selectFallbackImage(images, fallbackFormat) {
238
+ if (fallbackFormat) {
239
+ const preferred = largestImageForFormat(images, fallbackFormat);
240
+ if (preferred) {
241
+ return preferred;
242
+ }
243
+ }
244
+ return images.reduce((selected, image) => {
245
+ if (!selected || image.width > selected.width) {
246
+ return image;
247
+ }
248
+ return selected;
249
+ }, undefined);
250
+ }
251
+ function largestImageForFormat(images, format) {
252
+ return images.reduce((selected, image) => {
253
+ if (image.format !== format) {
254
+ return selected;
255
+ }
256
+ if (!selected || image.width > selected.width) {
257
+ return image;
258
+ }
259
+ return selected;
260
+ }, undefined);
261
+ }
262
+ function withOptionalSizes(source, sizes) {
263
+ return { ...source, ...optionalSizes(sizes) };
264
+ }
265
+ function optionalSizes(sizes) {
266
+ return sizes === undefined ? {} : { sizes };
267
+ }
268
+ function renderElement(name, attrs) {
269
+ const renderedAttrs = attrs
270
+ .filter((attr) => attr[1] !== undefined)
271
+ .map(([attrName, value]) => `${attrName}="${escapeAttributeValue(String(value))}"`)
272
+ .join(" ");
273
+ return `<${name}${renderedAttrs ? ` ${renderedAttrs}` : ""}>`;
274
+ }
275
+ function escapeAttributeValue(value) {
276
+ return value.replace(/[&<>"']/g, (character) => {
277
+ switch (character) {
278
+ case "&":
279
+ return "&amp;";
280
+ case "<":
281
+ return "&lt;";
282
+ case ">":
283
+ return "&gt;";
284
+ case '"':
285
+ return "&quot;";
286
+ case "'":
287
+ return "&#39;";
288
+ default:
289
+ return character;
290
+ }
291
+ });
292
+ }
@@ -0,0 +1 @@
1
+ export * from "./core.js";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from "./core.js";
@@ -0,0 +1,157 @@
1
+ export type SvelteKitAuditOptions = {
2
+ routesDir: string;
3
+ paramsDir?: string;
4
+ migrationPlan?: boolean;
5
+ };
6
+ export type SvelteKitAuditReport = {
7
+ version: 1;
8
+ routesDir: string;
9
+ paramsDir?: string;
10
+ pages: SvelteKitPageAudit[];
11
+ endpoints: SvelteKitEndpointAudit[];
12
+ params: SvelteKitParamMatcherAudit[];
13
+ warnings: SvelteKitAuditWarning[];
14
+ migrationInventory: SvelteKitMigrationInventory;
15
+ migrationPlan?: SvelteKitMigrationPlan;
16
+ summary: {
17
+ pages: number;
18
+ endpoints: number;
19
+ dynamicRoutes: number;
20
+ endpointKinds: Partial<Record<SvelteKitEndpointKind, number>>;
21
+ warnings: number;
22
+ };
23
+ };
24
+ export type SvelteKitMigrationInventory = {
25
+ layouts: SvelteKitLayoutInventory[];
26
+ routeFiles: SvelteKitRouteFileInventory[];
27
+ componentHints: SvelteKitComponentHint[];
28
+ checklist: SvelteKitMigrationChecklistCategory[];
29
+ };
30
+ export type SvelteKitLayoutInventory = {
31
+ routePath: string;
32
+ file: string;
33
+ kind: "layout" | "layoutServer" | "layoutUniversal";
34
+ imports: string[];
35
+ components: string[];
36
+ layoutReset?: true;
37
+ };
38
+ export type SvelteKitRouteFileInventory = {
39
+ routePath: string;
40
+ file: string;
41
+ kind: SvelteKitRouteFileKind;
42
+ usesLoad: boolean;
43
+ usesActions: boolean;
44
+ usesRedirect: boolean;
45
+ usesError404: boolean;
46
+ exportsPrerender: boolean;
47
+ usesEntries: boolean;
48
+ layoutReset?: true;
49
+ };
50
+ export type SvelteKitRouteFileKind = "endpoint" | "layout" | "layoutServer" | "layoutUniversal" | "page" | "pageServer" | "pageUniversal";
51
+ export type SvelteKitComponentHint = {
52
+ file: string;
53
+ imports: string[];
54
+ components: string[];
55
+ };
56
+ export type SvelteKitMigrationChecklistCategory = {
57
+ category: string;
58
+ items: string[];
59
+ };
60
+ export type SvelteKitMigrationPlan = {
61
+ routeFamilies: SvelteKitRouteFamilyPlan[];
62
+ artifactFamilies: SvelteKitArtifactFamilyPlan[];
63
+ paramMatchers: SvelteKitParamMatcherPlan[];
64
+ collisionTests: SvelteKitCollisionTestPlan[];
65
+ };
66
+ export type SvelteKitMigrationStubFile = {
67
+ path: "data.ts" | "routes.ts" | "artifacts.ts" | "param-matchers.ts" | "build.ts" | "collision-tests.md";
68
+ contents: string;
69
+ };
70
+ export type SvelteKitMigrationStubEmitOptions = {
71
+ overwrite?: boolean;
72
+ };
73
+ export type SvelteKitRouteFamilyPlan = {
74
+ name: string;
75
+ routePath: string;
76
+ pattern: string;
77
+ files: string[];
78
+ params: SvelteKitRouteParam[];
79
+ usesPageServer: boolean;
80
+ usesUniversalLoad: boolean;
81
+ usesEntries: boolean;
82
+ usesActions?: true;
83
+ layoutFiles?: string[];
84
+ paramMatchers: Record<string, string>;
85
+ };
86
+ export type SvelteKitArtifactFamilyPlan = {
87
+ name: string;
88
+ routePath: string;
89
+ pattern: string;
90
+ file: string;
91
+ kind: SvelteKitEndpointKind;
92
+ renderer: "artifactResponse" | "csvArtifact" | "jsonArtifact" | "textArtifact" | "xmlArtifact";
93
+ params: SvelteKitRouteParam[];
94
+ paramMatchers: Record<string, string>;
95
+ usesResponseMetadata: boolean;
96
+ usesEntries?: true;
97
+ methods?: string[];
98
+ nonStaticMethods?: string[];
99
+ };
100
+ export type SvelteKitParamMatcherPlan = {
101
+ name: string;
102
+ file: string;
103
+ usedBy: string[];
104
+ };
105
+ export type SvelteKitCollisionTestPlan = {
106
+ code: SvelteKitAuditWarning["code"];
107
+ path: string;
108
+ message: string;
109
+ };
110
+ export type SvelteKitPageAudit = {
111
+ routePath: string;
112
+ stoneagePattern: string;
113
+ files: string[];
114
+ params: SvelteKitRouteParam[];
115
+ hasPage: boolean;
116
+ hasPageServer: boolean;
117
+ hasPageUniversal: boolean;
118
+ usesUniversalLoad: boolean;
119
+ usesActions: boolean;
120
+ usesEntries: boolean;
121
+ exportsPrerender: boolean;
122
+ usesRedirect: boolean;
123
+ usesError404: boolean;
124
+ };
125
+ export type SvelteKitEndpointAudit = {
126
+ routePath: string;
127
+ stoneagePattern: string;
128
+ file: string;
129
+ params: SvelteKitRouteParam[];
130
+ kind: SvelteKitEndpointKind;
131
+ methods: string[];
132
+ usesJson: boolean;
133
+ usesResponse: boolean;
134
+ usesEntries: boolean;
135
+ exportsPrerender: boolean;
136
+ usesRedirect: boolean;
137
+ usesError404: boolean;
138
+ };
139
+ export type SvelteKitRouteParam = {
140
+ name: string;
141
+ matcher?: string;
142
+ rest?: boolean;
143
+ optional?: boolean;
144
+ };
145
+ export type SvelteKitParamMatcherAudit = {
146
+ name: string;
147
+ file: string;
148
+ };
149
+ export type SvelteKitAuditWarning = {
150
+ code: "param.matcher_missing" | "endpoint.non_static_method" | "route.duplicate_group_output" | "route.optional_param" | "route.page_actions" | "route.page_artifact_same_path" | "route.rest_param" | "route.static_dynamic_shadow" | "route.sibling_dynamic_without_matcher";
151
+ path: string;
152
+ message: string;
153
+ };
154
+ export type SvelteKitEndpointKind = "binary" | "csv" | "json" | "txt" | "unknown" | "xml";
155
+ export declare function auditSvelteKitPublicSurface(options: SvelteKitAuditOptions): Promise<SvelteKitAuditReport>;
156
+ export declare function createSvelteKitMigrationStubs(plan: SvelteKitMigrationPlan): SvelteKitMigrationStubFile[];
157
+ export declare function emitSvelteKitMigrationStubs(plan: SvelteKitMigrationPlan, outDir: string, options?: SvelteKitMigrationStubEmitOptions): Promise<SvelteKitMigrationStubFile[]>;