@tileflow/dev 0.1.0-alpha.3 → 0.1.0-alpha.31
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/LICENSE +201 -0
- package/README.md +201 -5
- package/THIRD_PARTY_NOTICES.md +87 -0
- package/dist/artifacts.d.ts +265 -0
- package/dist/artifacts.js +81 -0
- package/dist/chunk-2QXKVQ6O.js +3062 -0
- package/dist/chunk-7C4F6GAX.js +170 -0
- package/dist/chunk-AQUGMUZW.js +324 -0
- package/dist/chunk-D4OE4ITL.js +13 -0
- package/dist/chunk-IR22ZTQW.js +2392 -0
- package/dist/chunk-KDFTE4Q5.js +156 -0
- package/dist/chunk-ONYQNLNA.js +1108 -0
- package/dist/chunk-UTRO4IBQ.js +671 -0
- package/dist/chunk-XHJBCVBB.js +241 -0
- package/dist/config.d.ts +33 -0
- package/dist/config.js +27 -0
- package/dist/fonts.d.ts +47 -0
- package/dist/fonts.js +16 -0
- package/dist/icons.d.ts +339 -0
- package/dist/icons.js +39 -0
- package/dist/inspect.d.ts +149 -0
- package/dist/inspect.js +805 -0
- package/dist/preview-assets-MVUJJ5XR.js +148 -0
- package/dist/preview.d.ts +71 -0
- package/dist/preview.js +2574 -0
- package/dist/server.d.ts +82 -0
- package/dist/server.js +2094 -0
- package/dist/tilesets.d.ts +89 -0
- package/dist/tilesets.js +6 -0
- package/dist/validation.d.ts +65 -0
- package/dist/validation.js +24 -0
- package/docs/artifact-lifecycle.md +152 -0
- package/docs/bounded-pmtiles-resource-inspection.md +21 -0
- package/docs/bounded-vector-feature-inspection.md +100 -0
- package/docs/local-comparison-workbench-and-compiler-inspection.md +227 -0
- package/docs/native-artifacts.md +285 -0
- package/docs/native-icons.md +129 -0
- package/docs/structured-config-inspection-and-diagnostics.md +39 -0
- package/package.json +77 -13
- package/dist/index.d.ts +0 -54
- package/dist/index.js +0 -314
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// src/asset-directories.ts
|
|
2
|
+
import { realpath, stat } from "fs/promises";
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
import { dirname, isAbsolute, relative, resolve, sep } from "path";
|
|
5
|
+
import {
|
|
6
|
+
isTileflowLocalDirectory,
|
|
7
|
+
tileflowLocalDirectoryMessage
|
|
8
|
+
} from "@tileflow/core";
|
|
9
|
+
var TileflowAssetDirectoryError = class extends Error {
|
|
10
|
+
code = "ASSET_DIRECTORY_INVALID";
|
|
11
|
+
issues;
|
|
12
|
+
phase = "asset-directory-resolution";
|
|
13
|
+
constructor(kind, issues) {
|
|
14
|
+
super(
|
|
15
|
+
[
|
|
16
|
+
`Invalid Tileflow ${kind} directories`,
|
|
17
|
+
...issues.map((issue) => `- ${issue.path}: ${issue.message}`)
|
|
18
|
+
].join("\n")
|
|
19
|
+
);
|
|
20
|
+
this.name = "TileflowAssetDirectoryError";
|
|
21
|
+
this.issues = issues;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
async function resolveTileflowAssetDirectories(directories, options) {
|
|
25
|
+
const issues = [];
|
|
26
|
+
let realCwd;
|
|
27
|
+
let realBaseDirectory;
|
|
28
|
+
try {
|
|
29
|
+
realCwd = await realpath(options.cwd);
|
|
30
|
+
if (!(await stat(realCwd)).isDirectory()) throw new Error("Working tree is not a directory");
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new TileflowAssetDirectoryError(options.kind, [
|
|
33
|
+
{ message: filesystemMessage(error, "Selected working tree was not found"), path: "cwd" }
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
realBaseDirectory = await realpath(options.baseDirectory ?? options.cwd);
|
|
38
|
+
if (!(await stat(realBaseDirectory)).isDirectory()) {
|
|
39
|
+
throw new Error("Config base is not a directory");
|
|
40
|
+
}
|
|
41
|
+
if (!isPathInside(realCwd, realBaseDirectory)) {
|
|
42
|
+
throw new Error("Config base directory escapes the selected working tree");
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new TileflowAssetDirectoryError(options.kind, [
|
|
46
|
+
{
|
|
47
|
+
message: filesystemMessage(error, "Config base directory was not found"),
|
|
48
|
+
path: "baseDirectory"
|
|
49
|
+
}
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
52
|
+
const result = [];
|
|
53
|
+
const seen = /* @__PURE__ */ new Map();
|
|
54
|
+
for (const [index, directory] of directories.entries()) {
|
|
55
|
+
const configPath = `${options.configPath}.${index + (options.ordinalOffset ?? 0)}`;
|
|
56
|
+
let candidate;
|
|
57
|
+
let containmentRoot;
|
|
58
|
+
let packageOwned = false;
|
|
59
|
+
if (typeof directory === "string") {
|
|
60
|
+
if (!isTileflowLocalDirectory(directory)) {
|
|
61
|
+
issues.push({
|
|
62
|
+
message: tileflowLocalDirectoryMessage,
|
|
63
|
+
path: configPath
|
|
64
|
+
});
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
candidate = resolve(realBaseDirectory, directory);
|
|
68
|
+
containmentRoot = realCwd;
|
|
69
|
+
} else if (isPackageDirectory(directory)) {
|
|
70
|
+
packageOwned = true;
|
|
71
|
+
let packageRoot;
|
|
72
|
+
try {
|
|
73
|
+
validatePackageDirectory(directory);
|
|
74
|
+
const requireFromProject = createRequire(resolve(realCwd, "__tileflow_config__.cjs"));
|
|
75
|
+
let packageJson;
|
|
76
|
+
try {
|
|
77
|
+
packageJson = requireFromProject.resolve(`${directory.package}/package.json`);
|
|
78
|
+
} catch {
|
|
79
|
+
packageJson = createRequire(import.meta.url).resolve(`${directory.package}/package.json`);
|
|
80
|
+
}
|
|
81
|
+
packageRoot = dirname(packageJson);
|
|
82
|
+
containmentRoot = await realpath(packageRoot);
|
|
83
|
+
candidate = resolve(containmentRoot, ...directory.path.split("/"));
|
|
84
|
+
} catch (error) {
|
|
85
|
+
issues.push({
|
|
86
|
+
message: error instanceof Error ? error.message : "Package directory could not be resolved",
|
|
87
|
+
path: configPath
|
|
88
|
+
});
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
issues.push({
|
|
93
|
+
message: "Expected a relative directory or imported package-directory descriptor",
|
|
94
|
+
path: configPath
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const realCandidate = await realpath(candidate);
|
|
100
|
+
if (!(await stat(realCandidate)).isDirectory()) {
|
|
101
|
+
throw new Error("Asset source must be a directory");
|
|
102
|
+
}
|
|
103
|
+
if (!isPathInside(containmentRoot, realCandidate)) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
packageOwned ? "Package asset directory escapes its installed package" : "Asset directory escapes the selected working tree"
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (options.target === "hosted" && !packageOwned && !isPathInside(realCwd, realCandidate)) {
|
|
109
|
+
throw new Error("Hosted asset directories must remain inside the selected working tree");
|
|
110
|
+
}
|
|
111
|
+
const previous = seen.get(realCandidate);
|
|
112
|
+
if (previous) {
|
|
113
|
+
issues.push({ message: `Directory duplicates ${previous}`, path: configPath });
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
seen.set(realCandidate, configPath);
|
|
117
|
+
result.push({
|
|
118
|
+
authoring: directory,
|
|
119
|
+
configPath,
|
|
120
|
+
containmentRoot,
|
|
121
|
+
packageOwned,
|
|
122
|
+
realPath: realCandidate,
|
|
123
|
+
watch: !packageOwned
|
|
124
|
+
});
|
|
125
|
+
} catch (error) {
|
|
126
|
+
issues.push({
|
|
127
|
+
message: filesystemMessage(error, "Asset directory was not found"),
|
|
128
|
+
path: configPath
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (issues.length > 0) throw new TileflowAssetDirectoryError(options.kind, issues);
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
function isPackageDirectory(value) {
|
|
136
|
+
return Boolean(
|
|
137
|
+
value && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype && value.kind === "package-directory"
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
function validatePackageDirectory(directory) {
|
|
141
|
+
const keys = Object.keys(directory).sort();
|
|
142
|
+
if (keys.join(",") !== "kind,package,path") {
|
|
143
|
+
throw new Error("Package directory accepts exactly kind, package, and path");
|
|
144
|
+
}
|
|
145
|
+
if (!/^(?:@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*|[a-z0-9][a-z0-9._-]*)$/u.test(
|
|
146
|
+
directory.package
|
|
147
|
+
)) {
|
|
148
|
+
throw new Error("Package directory has an invalid npm package name");
|
|
149
|
+
}
|
|
150
|
+
if (!directory.path || directory.path.startsWith("/") || directory.path.includes("\\") || directory.path.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
|
|
151
|
+
throw new Error("Package directory path must be a portable package-relative path");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function isPathInside(root, candidate) {
|
|
155
|
+
const pathFromRoot = relative(root, candidate);
|
|
156
|
+
return pathFromRoot === "" || !isAbsolute(pathFromRoot) && pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${sep}`);
|
|
157
|
+
}
|
|
158
|
+
function filesystemMessage(error, fallback) {
|
|
159
|
+
if (error && typeof error === "object" && "code" in error) {
|
|
160
|
+
const code = String(error.code ?? "");
|
|
161
|
+
if (code === "ENOENT") return fallback;
|
|
162
|
+
if (code === "EACCES" || code === "EPERM") return "Asset directory is not readable";
|
|
163
|
+
}
|
|
164
|
+
return error instanceof Error && error.message ? error.message : fallback;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export {
|
|
168
|
+
TileflowAssetDirectoryError,
|
|
169
|
+
resolveTileflowAssetDirectories
|
|
170
|
+
};
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isPathWithin
|
|
3
|
+
} from "./chunk-D4OE4ITL.js";
|
|
4
|
+
|
|
5
|
+
// src/config.ts
|
|
6
|
+
import { createJiti } from "jiti";
|
|
7
|
+
import { existsSync, realpathSync } from "fs";
|
|
8
|
+
import { createRequire } from "module";
|
|
9
|
+
import { dirname, resolve, sep } from "path";
|
|
10
|
+
import {
|
|
11
|
+
parseResolvedTileflowMap,
|
|
12
|
+
parseTileflowMap,
|
|
13
|
+
tileflowCaptureSceneNameSchema,
|
|
14
|
+
tileflowCaptureShotNameSchema,
|
|
15
|
+
TileflowResolvedMapValidationError
|
|
16
|
+
} from "@tileflow/core";
|
|
17
|
+
import { collectTileflowMapBuildLineage } from "@tileflow/core/build";
|
|
18
|
+
var defaultTileflowApiUrl = "https://api.tileflow.dev";
|
|
19
|
+
var defaultTileflowConfigPath = "tileflow.config.ts";
|
|
20
|
+
var defaultTileflowManifestPath = "public/tileflow/manifest.json";
|
|
21
|
+
var TileflowValidationError = class extends Error {
|
|
22
|
+
code = "CONFIG_INVALID";
|
|
23
|
+
messages;
|
|
24
|
+
phase = "config-validation";
|
|
25
|
+
constructor(messages) {
|
|
26
|
+
super("Invalid Tileflow config");
|
|
27
|
+
this.name = "TileflowValidationError";
|
|
28
|
+
this.messages = messages;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
async function loadTileflowConfig(configPath = defaultTileflowConfigPath, options = {}) {
|
|
32
|
+
return (await loadTileflowConfigWithInputs(configPath, options)).project;
|
|
33
|
+
}
|
|
34
|
+
async function loadTileflowConfigWithInputs(configPath = defaultTileflowConfigPath, options = {}) {
|
|
35
|
+
const cwd = options.cwd ?? process.cwd();
|
|
36
|
+
const resolvedPath = resolve(cwd, configPath);
|
|
37
|
+
if (!existsSync(resolvedPath)) {
|
|
38
|
+
throw new Error(`Config not found: ${configPath}`);
|
|
39
|
+
}
|
|
40
|
+
if (options.fresh) {
|
|
41
|
+
clearTileflowLocalModuleCache(resolvedPath, cwd);
|
|
42
|
+
}
|
|
43
|
+
const jiti = createJiti(import.meta.url, {
|
|
44
|
+
fsCache: false,
|
|
45
|
+
moduleCache: true
|
|
46
|
+
});
|
|
47
|
+
const imported = await jiti.import(resolvedPath, { default: true });
|
|
48
|
+
const allowWorkspace = /\.workspace\.(?:[cm]?[jt]s)$/u.test(resolvedPath);
|
|
49
|
+
const project = asTileflowBuildCatalog(imported, { allowWorkspace });
|
|
50
|
+
return {
|
|
51
|
+
authoringMaps: getTileflowAuthoringMaps(imported, project, allowWorkspace),
|
|
52
|
+
configFile: resolvedPath,
|
|
53
|
+
inputFiles: collectTileflowConfigInputs(resolvedPath, cwd),
|
|
54
|
+
project
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function loadValidTileflowConfig(configPath = defaultTileflowConfigPath, options = {}) {
|
|
58
|
+
const project = await loadTileflowConfig(configPath, options);
|
|
59
|
+
assertValidTileflowConfig(project);
|
|
60
|
+
return project;
|
|
61
|
+
}
|
|
62
|
+
async function loadValidTileflowConfigWithInputs(configPath = defaultTileflowConfigPath, options = {}) {
|
|
63
|
+
const loaded = await loadTileflowConfigWithInputs(configPath, options);
|
|
64
|
+
assertValidTileflowConfig(loaded.project);
|
|
65
|
+
return loaded;
|
|
66
|
+
}
|
|
67
|
+
function assertValidTileflowConfig(project) {
|
|
68
|
+
const messages = [];
|
|
69
|
+
for (const [mapId, map] of Object.entries(project.maps)) {
|
|
70
|
+
try {
|
|
71
|
+
parseResolvedTileflowMap(map);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
messages.push({
|
|
74
|
+
level: "error",
|
|
75
|
+
path: `maps.${mapId}`,
|
|
76
|
+
message: error instanceof Error ? error.message : String(error)
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const [sceneId, scene] of Object.entries(project.scenes ?? {})) {
|
|
81
|
+
if (!Object.hasOwn(project.maps, scene.map)) {
|
|
82
|
+
messages.push({
|
|
83
|
+
level: "error",
|
|
84
|
+
path: `scenes.${sceneId}.map`,
|
|
85
|
+
message: `Unknown Tileflow map "${scene.map}".`
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
for (const [shotId, shot] of Object.entries(project.shots ?? {})) {
|
|
90
|
+
if (!Object.hasOwn(project.maps, shot.map)) {
|
|
91
|
+
messages.push({
|
|
92
|
+
level: "error",
|
|
93
|
+
path: `shots.${shotId}.map`,
|
|
94
|
+
message: `Unknown Tileflow map "${shot.map}".`
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (messages.length > 0) throw new TileflowValidationError(messages);
|
|
99
|
+
}
|
|
100
|
+
function asTileflowBuildCatalog(input, options = {}) {
|
|
101
|
+
if (isRecord(input) && isRecord(input.maps)) {
|
|
102
|
+
if (!options.allowWorkspace) {
|
|
103
|
+
throw tileflowConfigValidationError(
|
|
104
|
+
"maps",
|
|
105
|
+
new Error(
|
|
106
|
+
"tileflow.config.ts must export one map. Multi-map catalogs are internal workspaces and must use a *.workspace.ts file."
|
|
107
|
+
)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const unknownKey = Object.keys(input).find((key) => key !== "maps");
|
|
111
|
+
if (unknownKey) {
|
|
112
|
+
throw tileflowConfigValidationError(
|
|
113
|
+
unknownKey,
|
|
114
|
+
new Error(`Tileflow workspace has unrecognized key "${unknownKey}".`)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const entries = Object.entries(input.maps).map(([key, value]) => {
|
|
118
|
+
const map2 = value;
|
|
119
|
+
const resolved2 = parseTileflowCatalogMap(map2, `maps.${key}`);
|
|
120
|
+
if (key !== resolved2.id) {
|
|
121
|
+
throw tileflowConfigValidationError(
|
|
122
|
+
`maps.${key}`,
|
|
123
|
+
new Error(`Tileflow workspace map key "${key}" must match the map id "${resolved2.id}".`)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return [
|
|
127
|
+
key,
|
|
128
|
+
resolved2,
|
|
129
|
+
collectTileflowMapBuildLineage(map2),
|
|
130
|
+
normalizeSingularMapScenes(map2, resolved2.id),
|
|
131
|
+
normalizeSingularMapShots(map2, resolved2.id)
|
|
132
|
+
];
|
|
133
|
+
});
|
|
134
|
+
if (entries.length === 0) {
|
|
135
|
+
throw tileflowConfigValidationError(
|
|
136
|
+
"maps",
|
|
137
|
+
new Error("Tileflow workspace must contain at least one map.")
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const scenes2 = /* @__PURE__ */ new Map();
|
|
141
|
+
for (const [mapId, , , mapScenes] of entries) {
|
|
142
|
+
for (const [sceneId, scene] of Object.entries(mapScenes ?? {})) {
|
|
143
|
+
if (scenes2.has(sceneId)) {
|
|
144
|
+
throw tileflowConfigValidationError(
|
|
145
|
+
`maps.${mapId}.scenes.${sceneId}`,
|
|
146
|
+
new Error(`Tileflow workspace scene "${sceneId}" is already defined by another map.`)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
scenes2.set(sceneId, scene);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const shots2 = /* @__PURE__ */ new Map();
|
|
153
|
+
for (const [mapId, , , , mapShots] of entries) {
|
|
154
|
+
for (const [shotId, shot] of Object.entries(mapShots ?? {})) {
|
|
155
|
+
if (shots2.has(shotId)) {
|
|
156
|
+
throw tileflowConfigValidationError(
|
|
157
|
+
`maps.${mapId}.shots.${shotId}`,
|
|
158
|
+
new Error(`Tileflow workspace shot "${shotId}" is already defined by another map.`)
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
shots2.set(shotId, shot);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
mapMetadata: Object.fromEntries(
|
|
166
|
+
entries.map(([id, map2, lineage]) => [id, { id, lineage, version: map2.version }])
|
|
167
|
+
),
|
|
168
|
+
maps: Object.fromEntries(entries.map(([id, map2]) => [id, map2])),
|
|
169
|
+
...scenes2.size > 0 ? { scenes: Object.fromEntries(scenes2) } : {},
|
|
170
|
+
...shots2.size > 0 ? { shots: Object.fromEntries(shots2) } : {}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const map = input;
|
|
174
|
+
const resolved = parseTileflowCatalogMap(map, "");
|
|
175
|
+
let scenes;
|
|
176
|
+
try {
|
|
177
|
+
scenes = normalizeSingularMapScenes(map, resolved.id);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
throw tileflowConfigValidationError("scenes", error);
|
|
180
|
+
}
|
|
181
|
+
let shots;
|
|
182
|
+
try {
|
|
183
|
+
shots = normalizeSingularMapShots(map, resolved.id);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
throw tileflowConfigValidationError("shots", error);
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
mapMetadata: {
|
|
189
|
+
[resolved.id]: {
|
|
190
|
+
id: resolved.id,
|
|
191
|
+
lineage: collectTileflowMapBuildLineage(map),
|
|
192
|
+
version: resolved.version
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
maps: { [resolved.id]: resolved },
|
|
196
|
+
...scenes ? { scenes } : {},
|
|
197
|
+
...shots ? { shots } : {}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function parseTileflowCatalogMap(map, path) {
|
|
201
|
+
try {
|
|
202
|
+
return parseTileflowMap(map);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
throw tileflowConfigValidationError(path, error);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function getTileflowAuthoringMaps(input, project, allowWorkspace) {
|
|
208
|
+
if (allowWorkspace && isRecord(input) && isRecord(input.maps)) {
|
|
209
|
+
const maps = input.maps;
|
|
210
|
+
return Object.fromEntries(
|
|
211
|
+
Object.keys(project.maps).map((mapId2) => [mapId2, maps[mapId2]])
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
const [mapId] = Object.keys(project.maps);
|
|
215
|
+
return mapId ? { [mapId]: input } : {};
|
|
216
|
+
}
|
|
217
|
+
function tileflowConfigValidationError(path, error) {
|
|
218
|
+
if (error instanceof TileflowValidationError) return error;
|
|
219
|
+
if (error instanceof TileflowResolvedMapValidationError) {
|
|
220
|
+
return new TileflowValidationError(
|
|
221
|
+
error.messages.map((message) => ({
|
|
222
|
+
...message,
|
|
223
|
+
path: prefixValidationPath(path, message.path)
|
|
224
|
+
}))
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
return new TileflowValidationError([
|
|
228
|
+
{
|
|
229
|
+
level: "error",
|
|
230
|
+
path: path || "map",
|
|
231
|
+
message: error instanceof Error ? error.message : String(error)
|
|
232
|
+
}
|
|
233
|
+
]);
|
|
234
|
+
}
|
|
235
|
+
function prefixValidationPath(prefix, path) {
|
|
236
|
+
const child = path === "config" ? "" : path;
|
|
237
|
+
if (!prefix) return child || "map";
|
|
238
|
+
return child ? `${prefix}.${child}` : prefix;
|
|
239
|
+
}
|
|
240
|
+
function normalizeSingularMapScenes(map, mapId) {
|
|
241
|
+
if (!map.scenes) return void 0;
|
|
242
|
+
return Object.fromEntries(
|
|
243
|
+
Object.entries(map.scenes).map(([sceneId, scene]) => {
|
|
244
|
+
const normalizedSceneId = tileflowCaptureSceneNameSchema.parse(sceneId);
|
|
245
|
+
return [normalizedSceneId, { ...scene, map: mapId }];
|
|
246
|
+
})
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
function normalizeSingularMapShots(map, mapId) {
|
|
250
|
+
if (!map.shots) return void 0;
|
|
251
|
+
return Object.fromEntries(
|
|
252
|
+
Object.entries(map.shots).map(([shotId, shot]) => {
|
|
253
|
+
const normalizedShotId = tileflowCaptureShotNameSchema.parse(shotId);
|
|
254
|
+
return [normalizedShotId, { ...shot, map: mapId }];
|
|
255
|
+
})
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
function getTileflowMapNames(project) {
|
|
259
|
+
return Object.keys(project.maps);
|
|
260
|
+
}
|
|
261
|
+
function getFirstTileflowMapName(project) {
|
|
262
|
+
const [mapName] = getTileflowMapNames(project);
|
|
263
|
+
if (!mapName) {
|
|
264
|
+
throw new Error("Tileflow config must define at least one map.");
|
|
265
|
+
}
|
|
266
|
+
return mapName;
|
|
267
|
+
}
|
|
268
|
+
var localRequire = createRequire(import.meta.url);
|
|
269
|
+
function collectTileflowConfigInputs(configPath, cwd) {
|
|
270
|
+
const roots = [canonicalPath(cwd), canonicalPath(dirname(resolve(configPath)))];
|
|
271
|
+
const inputs = /* @__PURE__ */ new Set([canonicalPath(configPath)]);
|
|
272
|
+
const pending = [localRequire.cache[canonicalPath(configPath)]];
|
|
273
|
+
const visited = /* @__PURE__ */ new Set();
|
|
274
|
+
while (pending.length > 0) {
|
|
275
|
+
const module = pending.pop();
|
|
276
|
+
if (!module || visited.has(module.filename)) continue;
|
|
277
|
+
visited.add(module.filename);
|
|
278
|
+
const path = canonicalPath(module.filename);
|
|
279
|
+
if (!path.split(sep).includes("node_modules") && roots.some((root) => isPathWithin(root, path))) {
|
|
280
|
+
inputs.add(path);
|
|
281
|
+
}
|
|
282
|
+
pending.push(...module.children);
|
|
283
|
+
}
|
|
284
|
+
for (const cachePath of Object.keys(localRequire.cache)) {
|
|
285
|
+
const path = canonicalPath(cachePath);
|
|
286
|
+
if (!path.split(sep).includes("node_modules") && roots.some((root) => isPathWithin(root, path))) {
|
|
287
|
+
inputs.add(path);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return [...inputs].sort();
|
|
291
|
+
}
|
|
292
|
+
function clearTileflowLocalModuleCache(configPath, cwd) {
|
|
293
|
+
const roots = [canonicalPath(cwd), canonicalPath(dirname(resolve(configPath)))];
|
|
294
|
+
for (const cachePath of Object.keys(localRequire.cache)) {
|
|
295
|
+
if (cachePath.split(sep).includes("node_modules")) continue;
|
|
296
|
+
if (roots.some((root) => isPathWithin(root, canonicalPath(cachePath)))) {
|
|
297
|
+
delete localRequire.cache[cachePath];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function canonicalPath(path) {
|
|
302
|
+
try {
|
|
303
|
+
return realpathSync.native(resolve(path));
|
|
304
|
+
} catch {
|
|
305
|
+
return resolve(path);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function isRecord(value) {
|
|
309
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export {
|
|
313
|
+
defaultTileflowApiUrl,
|
|
314
|
+
defaultTileflowConfigPath,
|
|
315
|
+
defaultTileflowManifestPath,
|
|
316
|
+
TileflowValidationError,
|
|
317
|
+
loadTileflowConfig,
|
|
318
|
+
loadTileflowConfigWithInputs,
|
|
319
|
+
loadValidTileflowConfig,
|
|
320
|
+
loadValidTileflowConfigWithInputs,
|
|
321
|
+
assertValidTileflowConfig,
|
|
322
|
+
getTileflowMapNames,
|
|
323
|
+
getFirstTileflowMapName
|
|
324
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/path-safety.ts
|
|
2
|
+
import path from "path";
|
|
3
|
+
function isPathWithin(root, candidate) {
|
|
4
|
+
return isPathWithinWith(path, root, candidate);
|
|
5
|
+
}
|
|
6
|
+
function isPathWithinWith(pathApi, root, candidate) {
|
|
7
|
+
const relativePath = pathApi.relative(pathApi.resolve(root), pathApi.resolve(candidate));
|
|
8
|
+
return relativePath === "" || !pathApi.isAbsolute(relativePath) && relativePath !== ".." && !relativePath.startsWith(`..${pathApi.sep}`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
isPathWithin
|
|
13
|
+
};
|