tileflow 0.1.0-alpha.2 → 0.1.0-alpha.4

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 CHANGED
@@ -131,6 +131,41 @@ Each manifest command entry declares its `outputKind`, `outputVersion`, and stab
131
131
  `outputSchemaRef`. `raw-authoring-manifest` and `raw-config-reference` identify the two bootstrap
132
132
  documents above; `command-envelope` identifies the ordinary version-1 CLI contract.
133
133
 
134
+ ## Coordinates
135
+
136
+ These local commands require an explicitly provided compatible runtime distribution. No public
137
+ native runtime download is currently offered; package installation or the default setup URL does
138
+ not supply one. The verified production profile is Linux x64 on Debian 12, glibc 2.36 and Node 24.
139
+ The examples below use development artifacts and do not establish public platform support.
140
+
141
+ Coordinates commands use a locally provisioned execution release. They do not use Map
142
+ configuration, account state, or a Hosted fallback. Provision first from a distribution source, then
143
+ run the local commands against the active cache entry or an explicit runtime directory.
144
+
145
+ ```sh
146
+ tileflow setup coordinates --source /path/to/distribution.json --cache-dir /path/to/coordinates-cache --development --json
147
+ tileflow coordinates search --query ETRS89 --runtime-dir /path/to/installed-release --development --json
148
+ tileflow coordinates describe --id EPSG:25830 --runtime-dir /path/to/installed-release --development --json
149
+ tileflow coordinates operations --from EPSG:4258 --to EPSG:25832 --runtime-dir /path/to/installed-release --development --json
150
+ tileflow coordinates transform --from EPSG:4258 --to EPSG:25832 --positions '[[12,55]]' --runtime-dir /path/to/installed-release --development --json
151
+ ```
152
+
153
+ `setup coordinates --source` accepts a distribution URL, a local `distribution.json`, or a directory
154
+ that contains one. `--archive` is a local archive alias and rejects URLs. Setup verifies every asset
155
+ declared by the selected distribution before it records the active release. `--development` is
156
+ required for a development distribution.
157
+
158
+ Every execution command accepts `--runtime-dir`, `--release`, and `--development`. With no
159
+ `--runtime-dir`, it opens the cache entry selected by setup. `search` accepts `--query`, `describe`
160
+ accepts `--id`, `operations` accepts `--from` and `--to`, and `transform` also accepts JSON
161
+ `--positions`.
162
+
163
+ For a complete request, use exactly one of `--request '<json>'` or `--input <file|->`. These forms
164
+ cannot be combined with the command's convenience request flags. Input is limited to 32 KiB, must be
165
+ valid UTF-8 JSON, and `-` reads standard input. Success writes one JSON document to stdout. A
166
+ structured failure writes one JSON document to stderr and exits with status 1. Plain `--help` remains
167
+ human-readable; `--help --json` is a structured failure.
168
+
134
169
  `tileflow explain` compiles exactly one map/theme selection through `createStyleResult()` and emits
135
170
  its structured `report` and safe compiler `diagnostics`; it deliberately omits the usually large
136
171
  MapLibre `style`. In a singular config, `--map` is optional; a multi-map workbench requires it.
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import { hostname } from "os";
10
10
  import { dirname as dirname10, isAbsolute as isAbsolute4, relative as relative5, resolve as resolve7, sep as sep5 } from "path";
11
11
  import { createInterface } from "readline/promises";
12
12
  import pc3 from "picocolors";
13
+ import { createLocalCoordinates, setupCoordinates } from "@tileflow/coordinates-runtime";
13
14
  import {
14
15
  auditTileflowMapThemeValues,
15
16
  getTileflowStyleFontFaces,
@@ -1589,6 +1590,353 @@ function printHumanConfigInspection(inspection) {
1589
1590
  console.log("Run tileflow inspect --json for the complete machine-readable result.");
1590
1591
  }
1591
1592
 
1593
+ // src/coordinates-command.ts
1594
+ import { lstat as lstat2, open as open3 } from "fs/promises";
1595
+ import { CoordinatesSetupError } from "@tileflow/coordinates-runtime";
1596
+ import {
1597
+ CoordinatesContractError,
1598
+ coordinatesFailureSchema,
1599
+ coordinatesLimits,
1600
+ parseCoordinatesJsonRequest,
1601
+ parseCoordinatesRequest
1602
+ } from "@tileflow/coordinates/contract";
1603
+ var CoordinatesInputError = class extends Error {
1604
+ constructor(reason) {
1605
+ super("Coordinates input could not be read.");
1606
+ this.reason = reason;
1607
+ }
1608
+ reason;
1609
+ };
1610
+ function registerCoordinatesCommands(program2, dependencies) {
1611
+ const setup = program2.commands.find((command2) => command2.name() === "setup") ?? program2.command("setup").description("Provision local Tileflow tooling");
1612
+ configureCommand(
1613
+ setup.command("coordinates").description("Provision an offline Coordinates execution release").option("--source <url-or-path>", "distribution manifest URL or offline path").option("--archive <path>", "offline archive path alias").option("--cache-dir <path>", "Coordinates cache directory").option("--release <release-id>", "required Coordinates release identity").option("--development", "allow a development distribution").option("--json", "print one JSON receipt")
1614
+ ).action(async (options, command2) => {
1615
+ if (options.help) {
1616
+ printHelp(command2, options.json, () => printSetupFailure());
1617
+ return;
1618
+ }
1619
+ if (command2.args.length > 0 || options.source && options.archive || options.archive && isRemoteLocation(options.archive)) {
1620
+ printSetupFailure(new CoordinatesSetupError("COORDINATES_SETUP_INVALID_REQUEST"));
1621
+ return;
1622
+ }
1623
+ const controller = (dependencies.createSignalController ?? installSignalAbortController)();
1624
+ try {
1625
+ const receipt = await dependencies.setupCoordinates({
1626
+ source: options.archive ?? options.source,
1627
+ cacheDirectory: options.cacheDir,
1628
+ requiredReleaseId: options.release,
1629
+ allowDevelopment: options.development,
1630
+ signal: controller.signal
1631
+ });
1632
+ printDocument(receipt);
1633
+ } catch (error) {
1634
+ printSetupFailure(error);
1635
+ } finally {
1636
+ controller.close();
1637
+ }
1638
+ });
1639
+ const coordinates = program2.command("coordinates").description("Inspect and execute Coordinates requests using a provisioned local release");
1640
+ configureCommand(coordinates).option("--json", "print one JSON response");
1641
+ coordinates.action((options, command2) => {
1642
+ if (options.help) {
1643
+ printHelp(
1644
+ command2,
1645
+ options.json,
1646
+ () => printFailure(invalidRequest(null, "INVALID_VALUE", { path: ["command"] }))
1647
+ );
1648
+ return;
1649
+ }
1650
+ const reason = command2.args.length === 0 ? "INVALID_VALUE" : "UNKNOWN_FIELD";
1651
+ printFailure(invalidRequest(null, reason, { path: ["command"] }));
1652
+ });
1653
+ registerOperation(
1654
+ coordinates,
1655
+ "search",
1656
+ dependencies,
1657
+ (options) => parseRequest("search", options, { query: options.query })
1658
+ );
1659
+ registerOperation(
1660
+ coordinates,
1661
+ "describe",
1662
+ dependencies,
1663
+ (options) => parseRequest("describe", options, { id: options.id })
1664
+ );
1665
+ registerOperation(
1666
+ coordinates,
1667
+ "operations",
1668
+ dependencies,
1669
+ (options) => parseRequest("operations", options, { from: options.from, to: options.to })
1670
+ );
1671
+ registerOperation(
1672
+ coordinates,
1673
+ "transform",
1674
+ dependencies,
1675
+ (options) => parseTransformRequest(options)
1676
+ );
1677
+ }
1678
+ function registerOperation(coordinates, name, dependencies, parse3) {
1679
+ const command2 = configureCommand(
1680
+ coordinates.command(name).description(`Run the local Coordinates ${name} operation`).option("--request <json>", "complete JSON request").option("--input <path-or-dash>", "complete JSON request file, or - for standard input").option("--runtime-dir <path>", "prepared Coordinates runtime directory").option("--release <release-id>", "required Coordinates release identity").option("--development", "allow a development runtime").option("--json", "print one JSON response")
1681
+ );
1682
+ if (name === "search") command2.option("--query <text>", "search query");
1683
+ if (name === "describe") command2.option("--id <crs-id>", "CRS identifier");
1684
+ if (name === "operations" || name === "transform") {
1685
+ command2.option("--from <crs-id>", "source CRS identifier").option("--to <crs-id>", "target CRS identifier");
1686
+ }
1687
+ if (name === "transform") command2.option("--positions <json>", "JSON coordinate positions");
1688
+ command2.action(async (options, invoked) => {
1689
+ const parent = invoked.parent?.opts();
1690
+ const wantsHelp = options.help ?? parent?.help;
1691
+ const wantsJson = options.json ?? parent?.json;
1692
+ if (wantsHelp) {
1693
+ printHelp(
1694
+ invoked,
1695
+ wantsJson,
1696
+ () => printFailure(invalidRequest(name, "INVALID_VALUE", { path: ["command"] }))
1697
+ );
1698
+ return;
1699
+ }
1700
+ if (invoked.args.length > 0) {
1701
+ printFailure(invalidRequest(name, "UNKNOWN_FIELD", { path: ["options"] }));
1702
+ return;
1703
+ }
1704
+ const controller = (dependencies.createSignalController ?? installSignalAbortController)();
1705
+ let local;
1706
+ try {
1707
+ const request = await resolveRequest(
1708
+ name,
1709
+ options,
1710
+ parse3,
1711
+ dependencies.readInput,
1712
+ controller.signal
1713
+ );
1714
+ local = await dependencies.createLocalCoordinates({
1715
+ directory: options.runtimeDir,
1716
+ requiredReleaseId: options.release,
1717
+ allowDevelopment: options.development,
1718
+ signal: controller.signal
1719
+ });
1720
+ const response = await local[name](request, { signal: controller.signal });
1721
+ await local.close();
1722
+ local = void 0;
1723
+ printDocument(response);
1724
+ } catch (error) {
1725
+ printFailure(error, name);
1726
+ } finally {
1727
+ try {
1728
+ await local?.close();
1729
+ } catch {
1730
+ } finally {
1731
+ controller.close();
1732
+ }
1733
+ }
1734
+ });
1735
+ }
1736
+ function configureCommand(command2) {
1737
+ return command2.allowUnknownOption(true).allowExcessArguments(true).helpOption(false).option("-h, --help", "display help");
1738
+ }
1739
+ async function resolveRequest(command2, options, parse3, readInput = readCoordinatesInput, signal) {
1740
+ const rawRequest = options.request !== void 0 || options.input !== void 0;
1741
+ if (options.request !== void 0 && options.input !== void 0 || rawRequest && hasConvenienceOptions(command2, options)) {
1742
+ throw invalidRequest(command2, "INVALID_VALUE", { path: ["request"] });
1743
+ }
1744
+ if (options.request !== void 0) return parseCoordinatesJsonRequest(command2, options.request);
1745
+ if (options.input !== void 0) {
1746
+ try {
1747
+ const input = await readInput(options.input, signal ?? new AbortController().signal);
1748
+ if (signal?.aborted) throw cancelledRequest(command2);
1749
+ return parseCoordinatesJsonRequest(command2, input);
1750
+ } catch (error) {
1751
+ if (signal?.aborted) throw cancelledRequest(command2);
1752
+ if (error instanceof CoordinatesInputError) throw invalidRequest(command2, error.reason);
1753
+ throw error;
1754
+ }
1755
+ }
1756
+ return parse3(options);
1757
+ }
1758
+ function hasConvenienceOptions(command2, options) {
1759
+ if (command2 === "search") return options.query !== void 0;
1760
+ if (command2 === "describe") return options.id !== void 0;
1761
+ if (command2 === "operations") {
1762
+ const pair = options;
1763
+ return pair.from !== void 0 || pair.to !== void 0;
1764
+ }
1765
+ const transform = options;
1766
+ return transform.from !== void 0 || transform.to !== void 0 || transform.positions !== void 0;
1767
+ }
1768
+ function parseRequest(command2, options, request) {
1769
+ return parseCoordinatesRequest(command2, request);
1770
+ }
1771
+ function parseTransformRequest(options) {
1772
+ let positions;
1773
+ try {
1774
+ positions = options.positions === void 0 ? void 0 : JSON.parse(options.positions);
1775
+ } catch {
1776
+ throw invalidRequest("transform", "INVALID_JSON");
1777
+ }
1778
+ return parseCoordinatesRequest("transform", { from: options.from, to: options.to, positions });
1779
+ }
1780
+ async function readCoordinatesInput(input, signal) {
1781
+ if (input === "-") return readStandardInput(signal);
1782
+ assertNotAborted(signal);
1783
+ let info;
1784
+ try {
1785
+ info = await lstat2(input);
1786
+ } catch {
1787
+ throw new CoordinatesInputError("INVALID_VALUE");
1788
+ }
1789
+ if (!info.isFile() || info.isSymbolicLink()) throw new CoordinatesInputError("INVALID_VALUE");
1790
+ if (info.size > coordinatesLimits.maximumRequestBytes) {
1791
+ throw new CoordinatesInputError("REQUEST_TOO_LARGE");
1792
+ }
1793
+ const handle = await open3(input, "r");
1794
+ try {
1795
+ let length = 0;
1796
+ const bytes = Buffer.alloc(coordinatesLimits.maximumRequestBytes + 1);
1797
+ while (length < bytes.length) {
1798
+ assertNotAborted(signal);
1799
+ const { bytesRead } = await handle.read(bytes, length, bytes.length - length, length);
1800
+ if (bytesRead === 0) break;
1801
+ length += bytesRead;
1802
+ }
1803
+ assertNotAborted(signal);
1804
+ if (length > coordinatesLimits.maximumRequestBytes) {
1805
+ throw new CoordinatesInputError("REQUEST_TOO_LARGE");
1806
+ }
1807
+ return decodeInput(bytes.subarray(0, length));
1808
+ } finally {
1809
+ await handle.close();
1810
+ }
1811
+ }
1812
+ async function readStandardInput(signal) {
1813
+ const chunks = [];
1814
+ let length = 0;
1815
+ return new Promise((resolve8, reject) => {
1816
+ const finish = (result) => {
1817
+ process.stdin.removeListener("data", onData);
1818
+ process.stdin.removeListener("end", onEnd);
1819
+ process.stdin.removeListener("error", onError);
1820
+ signal.removeEventListener("abort", onAbort);
1821
+ result();
1822
+ };
1823
+ const onAbort = () => {
1824
+ process.stdin.pause();
1825
+ finish(() => reject(new CoordinatesInputError("INVALID_VALUE")));
1826
+ };
1827
+ const onData = (chunk) => {
1828
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1829
+ length += bytes.length;
1830
+ if (length > coordinatesLimits.maximumRequestBytes) {
1831
+ process.stdin.pause();
1832
+ finish(() => reject(new CoordinatesInputError("REQUEST_TOO_LARGE")));
1833
+ return;
1834
+ }
1835
+ chunks.push(bytes);
1836
+ };
1837
+ const onEnd = () => finish(() => resolve8(decodeInput(Buffer.concat(chunks))));
1838
+ const onError = () => finish(() => reject(new CoordinatesInputError("INVALID_VALUE")));
1839
+ if (signal.aborted) {
1840
+ onAbort();
1841
+ return;
1842
+ }
1843
+ process.stdin.on("data", onData);
1844
+ process.stdin.once("end", onEnd);
1845
+ process.stdin.once("error", onError);
1846
+ signal.addEventListener("abort", onAbort, { once: true });
1847
+ process.stdin.resume();
1848
+ });
1849
+ }
1850
+ function invalidRequest(command2, reason, details = {}) {
1851
+ return new CoordinatesContractError({
1852
+ schemaVersion: 1,
1853
+ ok: false,
1854
+ command: command2,
1855
+ releaseId: null,
1856
+ provenance: null,
1857
+ warnings: [],
1858
+ usage: null,
1859
+ error: { code: "COORDINATES_INVALID_REQUEST", reason, details, phase: "input" }
1860
+ });
1861
+ }
1862
+ function cancelledRequest(command2) {
1863
+ return new CoordinatesContractError({
1864
+ schemaVersion: 1,
1865
+ ok: false,
1866
+ command: command2,
1867
+ releaseId: null,
1868
+ provenance: null,
1869
+ warnings: [],
1870
+ usage: null,
1871
+ error: { code: "COORDINATES_CANCELLED", reason: "CANCELLED", details: {}, phase: "input" }
1872
+ });
1873
+ }
1874
+ function printDocument(document) {
1875
+ process.stdout.write(`${JSON.stringify(document)}
1876
+ `);
1877
+ }
1878
+ function printFailure(error, command2) {
1879
+ const document = serializeCoordinatesFailure(error, command2) ?? (command2 ? invalidRequest(command2, "INVALID_VALUE").toJSON() : setupFailureDocument());
1880
+ process.stderr.write(`${JSON.stringify(document)}
1881
+ `);
1882
+ process.exitCode = 1;
1883
+ }
1884
+ function printSetupFailure(error) {
1885
+ process.stderr.write(
1886
+ `${JSON.stringify(serializeSetupFailure(error) ?? setupFailureDocument())}
1887
+ `
1888
+ );
1889
+ process.exitCode = 1;
1890
+ }
1891
+ function serializeCoordinatesFailure(error, command2) {
1892
+ if (!(error instanceof CoordinatesContractError)) return void 0;
1893
+ const parsed = coordinatesFailureSchema.safeParse(error.toJSON());
1894
+ if (!parsed.success) return void 0;
1895
+ const failure = parsed.data;
1896
+ return new CoordinatesContractError({
1897
+ ...failure,
1898
+ command: failure.command ?? command2 ?? null
1899
+ }).toJSON();
1900
+ }
1901
+ function serializeSetupFailure(error) {
1902
+ if (!(error instanceof CoordinatesSetupError)) return void 0;
1903
+ const codes = /* @__PURE__ */ new Set([
1904
+ "COORDINATES_SETUP_INVALID_REQUEST",
1905
+ "COORDINATES_SETUP_RELEASE_UNAVAILABLE",
1906
+ "COORDINATES_SETUP_MANIFEST_INVALID",
1907
+ "COORDINATES_SETUP_ARTIFACT_UNAVAILABLE",
1908
+ "COORDINATES_SETUP_INTEGRITY_FAILED",
1909
+ "COORDINATES_SETUP_INSTALLATION_FAILED",
1910
+ "COORDINATES_SETUP_CANCELLED",
1911
+ "COORDINATES_SETUP_TIMEOUT"
1912
+ ]);
1913
+ if (!codes.has(error.code)) return void 0;
1914
+ return new CoordinatesSetupError(error.code, error.releaseId, error.assetId).toJSON();
1915
+ }
1916
+ function setupFailureDocument() {
1917
+ return new CoordinatesSetupError("COORDINATES_SETUP_INVALID_REQUEST").toJSON();
1918
+ }
1919
+ function printHelp(command2, json, printJson) {
1920
+ if (json) {
1921
+ printJson();
1922
+ return;
1923
+ }
1924
+ command2.outputHelp();
1925
+ }
1926
+ function isRemoteLocation(input) {
1927
+ return /^[A-Za-z][A-Za-z0-9+.-]*:\/\//u.test(input);
1928
+ }
1929
+ function assertNotAborted(signal) {
1930
+ if (signal.aborted) throw new CoordinatesInputError("INVALID_VALUE");
1931
+ }
1932
+ function decodeInput(bytes) {
1933
+ try {
1934
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
1935
+ } catch {
1936
+ throw new CoordinatesInputError("INVALID_VALUE");
1937
+ }
1938
+ }
1939
+
1592
1940
  // src/deploy-source.ts
1593
1941
  var overrideFields = [
1594
1942
  ["TILEFLOW_DEPLOY_REPOSITORY", "repository", 255],
@@ -3752,7 +4100,7 @@ import { z as z6 } from "zod";
3752
4100
 
3753
4101
  // src/tileset-upload.ts
3754
4102
  import { createHash } from "crypto";
3755
- import { open as open3, stat } from "fs/promises";
4103
+ import { open as open4, stat } from "fs/promises";
3756
4104
  import { z as z5 } from "zod";
3757
4105
  import { serializeCanonicalJson as serializeCanonicalJson2 } from "@tileflow/core";
3758
4106
  var teamTilesetPartSize = 16777216;
@@ -3816,7 +4164,7 @@ async function inspectNodeTeamTilesetArchive(path) {
3816
4164
  return checkedIdentity(value.dev, value.ino, value.mtimeNs, value.size);
3817
4165
  }
3818
4166
  async function openNodeTeamTilesetArchive(path, expectedIdentity) {
3819
- const handle = await open3(path, "r");
4167
+ const handle = await open4(path, "r");
3820
4168
  const identity = async () => {
3821
4169
  let pathStat;
3822
4170
  let handleStat;
@@ -4831,7 +5179,7 @@ var tilesetPurgeResponseSchema = z6.object({
4831
5179
  }).strict();
4832
5180
 
4833
5181
  // src/visual-command.ts
4834
- import { lstat as lstat3, readFile as readFile6 } from "fs/promises";
5182
+ import { lstat as lstat4, readFile as readFile6 } from "fs/promises";
4835
5183
  import { dirname as dirname9, isAbsolute as isAbsolute3, parse as parse2, relative as relative4, resolve as resolve6, sep as sep4 } from "path";
4836
5184
  import {
4837
5185
  analyzeTileflowCaptureReference,
@@ -4850,7 +5198,7 @@ import { compareCodeUnits as compareCodeUnits8 } from "@tileflow/core";
4850
5198
 
4851
5199
  // src/visual-compare-command.ts
4852
5200
  import { createHash as createHash3 } from "crypto";
4853
- import { lstat as lstat2, readFile as readFile5, realpath } from "fs/promises";
5201
+ import { lstat as lstat3, readFile as readFile5, realpath } from "fs/promises";
4854
5202
  import { basename as basename3, dirname as dirname8, extname, isAbsolute as isAbsolute2, parse, relative as relative3, resolve as resolve5, sep as sep3 } from "path";
4855
5203
  import {
4856
5204
  compareTileflowCapturesForReview,
@@ -5800,7 +6148,7 @@ async function readVisualCompareInventory(assetDirectory, inventoryPath, boundar
5800
6148
  await assertNoSymlinkComponents(dirname8(inventoryPath), boundary);
5801
6149
  let info;
5802
6150
  try {
5803
- info = await lstat2(inventoryPath);
6151
+ info = await lstat3(inventoryPath);
5804
6152
  } catch (error) {
5805
6153
  if (isMissingFile4(error)) return [];
5806
6154
  throw error;
@@ -5824,7 +6172,7 @@ async function readVisualCompareInventory(assetDirectory, inventoryPath, boundar
5824
6172
  await assertNoSymlinkComponents(dirname8(path), boundary);
5825
6173
  let assetInfo;
5826
6174
  try {
5827
- assetInfo = await lstat2(path);
6175
+ assetInfo = await lstat3(path);
5828
6176
  } catch (error) {
5829
6177
  if (isMissingFile4(error)) continue;
5830
6178
  throw error;
@@ -5986,7 +6334,7 @@ function pathsOverlap(left, right) {
5986
6334
  }
5987
6335
  async function assertDirectoryIfPresent(path) {
5988
6336
  try {
5989
- const info = await lstat2(path);
6337
+ const info = await lstat3(path);
5990
6338
  if (info.isSymbolicLink() || !info.isDirectory()) {
5991
6339
  throw new Error("Visual compare asset path must be a non-symlink directory.");
5992
6340
  }
@@ -5996,7 +6344,7 @@ async function assertDirectoryIfPresent(path) {
5996
6344
  }
5997
6345
  async function pathExists(path) {
5998
6346
  try {
5999
- await lstat2(path);
6347
+ await lstat3(path);
6000
6348
  return true;
6001
6349
  } catch (error) {
6002
6350
  if (isMissingFile4(error)) return false;
@@ -6350,7 +6698,7 @@ async function readOptionalArtifact(path, boundary, maximumBytes) {
6350
6698
  await assertNoSymlinkComponents(dirname9(path), boundary);
6351
6699
  let info;
6352
6700
  try {
6353
- info = await lstat3(path);
6701
+ info = await lstat4(path);
6354
6702
  } catch (error) {
6355
6703
  if (isMissingFile5(error)) return void 0;
6356
6704
  throw error;
@@ -6370,7 +6718,7 @@ async function readOptionalArtifact(path, boundary, maximumBytes) {
6370
6718
  async function readVisualReference(path) {
6371
6719
  let info;
6372
6720
  try {
6373
- info = await lstat3(path);
6721
+ info = await lstat4(path);
6374
6722
  } catch (error) {
6375
6723
  throw new TileflowCaptureError2("BASELINE_INVALID", "The visual reference PNG cannot be read.", {
6376
6724
  cause: error
@@ -6406,7 +6754,7 @@ async function resolveVisualDirectory(cwd, value, kind) {
6406
6754
  const boundary = isInside2(cwd, path) ? cwd : path;
6407
6755
  await assertNoSymlinkComponents(path, boundary);
6408
6756
  try {
6409
- const info = await lstat3(path);
6757
+ const info = await lstat4(path);
6410
6758
  if (info.isSymbolicLink() || !info.isDirectory()) {
6411
6759
  throw new Error(`Visual ${kind} path must be a non-symlink directory.`);
6412
6760
  }
@@ -6656,17 +7004,15 @@ program.command("validate").description("Validate a Tileflow config").option("-c
6656
7004
  phase: "hosted-validation"
6657
7005
  });
6658
7006
  }
6659
- const diagnostics = createTileflowStructuredDiagnostics2(
6660
- {
6661
- diagnostics: mapNames.flatMap(
6662
- (mapName) => auditTileflowMapThemeValues(project.maps[mapName]).filter(
6663
- ({ severity }) => severity === "warning"
6664
- )
6665
- )
6666
- },
6667
- process.cwd(),
6668
- { code: "VALIDATION_WARNING", phase: "theme-audit" }
7007
+ const themeWarnings = mapNames.flatMap(
7008
+ (mapName) => auditTileflowMapThemeValues(project.maps[mapName]).filter(
7009
+ ({ severity }) => severity === "warning"
7010
+ )
6669
7011
  );
7012
+ const diagnostics = themeWarnings.length === 0 ? [] : createTileflowStructuredDiagnostics2({ diagnostics: themeWarnings }, process.cwd(), {
7013
+ code: "VALIDATION_WARNING",
7014
+ phase: "theme-audit"
7015
+ });
6670
7016
  const checks = [
6671
7017
  "Config schema",
6672
7018
  "Icon asset closure",
@@ -7348,6 +7694,7 @@ registerConfigInspectCommand(inspectCommand, { defaultConfigPath });
7348
7694
  registerFeatureInspectCommand(inspectCommand, { defaultConfigPath });
7349
7695
  registerAiCommands(program, { defaultApiUrl, defaultConfigPath });
7350
7696
  registerCaptureCommands(program, { defaultConfigPath });
7697
+ registerCoordinatesCommands(program, { createLocalCoordinates, setupCoordinates });
7351
7698
  registerVisualCommands(program, {
7352
7699
  defaultConfigPath,
7353
7700
  openReport: (path) => openBrowser(path, true)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tileflow",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.4",
4
4
  "license": "Apache-2.0",
5
5
  "description": "CLI-only tools for Tileflow config, capture, visual testing, and deploy.",
6
6
  "type": "module",
@@ -25,10 +25,7 @@
25
25
  },
26
26
  "files": [
27
27
  "dist",
28
- "LICENSE",
29
- "NOTICE",
30
- "GENERATED_OUTPUT_LICENSE.md",
31
- "TRADEMARKS.md"
28
+ "LICENSE"
32
29
  ],
33
30
  "bin": {
34
31
  "tileflow": "./dist/index.js"
@@ -38,12 +35,14 @@
38
35
  },
39
36
  "dependencies": {
40
37
  "@hono/node-server": "2.0.10",
41
- "@tileflow/capture": ">=0.1.0-alpha.20 <0.1.0-beta.0",
42
- "@tileflow/core": ">=0.1.0-alpha.23 <0.1.0-beta.0",
43
- "@tileflow/dev": ">=0.1.0-alpha.21 <0.1.0-beta.0",
44
- "@tileflow/maps": ">=0.1.0-alpha.1 <0.1.0-beta.0",
45
- "hono": "4.12.34",
38
+ "@tileflow/capture": ">=0.1.0-alpha.22 <0.1.0-beta.0",
39
+ "@tileflow/coordinates": ">=0.1.0-alpha.0 <0.1.0-beta.0",
40
+ "@tileflow/coordinates-runtime": ">=0.1.0-alpha.1 <0.1.0-beta.0",
41
+ "@tileflow/core": ">=0.1.0-alpha.24 <0.1.0-beta.0",
42
+ "@tileflow/dev": ">=0.1.0-alpha.23 <0.1.0-beta.0",
43
+ "@tileflow/maps": ">=0.1.0-alpha.3 <0.1.0-beta.0",
46
44
  "commander": "^15.0.0",
45
+ "hono": "4.13.5",
47
46
  "picocolors": "^1.1.1",
48
47
  "zod": "^4.4.3"
49
48
  },
@@ -1,21 +0,0 @@
1
- # Tileflow generated-output grant
2
-
3
- In addition to the rights granted by the Apache License, Version 2.0, Tileflow.dev contributors
4
- grant recipients a perpetual, worldwide, non-exclusive, no-charge, royalty-free right to use,
5
- reproduce, modify, display, perform, distribute, sublicense, and commercialize the Tileflow-owned
6
- parts of output generated by this SDK.
7
-
8
- Covered output includes compiled map styles, manifests, receipts, sprites, glyph artifacts, static
9
- render descriptions, and other build artifacts produced from Tileflow maps or application maps.
10
- Original Tileflow map definitions, icons, and patterns distributed by `@tileflow/maps` may likewise
11
- be used, modified, embedded, deployed, and redistributed as part of personal or commercial maps and
12
- applications.
13
-
14
- This grant does not relicense third-party software, fonts, icons, map data, datasets, imagery, or
15
- other input material. Their licenses, attribution requirements, notices, and use restrictions
16
- continue to apply to the relevant source and generated output. You are responsible for having the
17
- rights necessary to process and distribute your own inputs.
18
-
19
- This grant does not provide access to Tileflow hosted services or grant rights in Tileflow names,
20
- logos, or marks. The SDK and generated output are provided without warranties or conditions beyond
21
- those required by applicable law.
package/NOTICE DELETED
@@ -1,9 +0,0 @@
1
- Tileflow SDK
2
- Copyright 2026 Tileflow.dev contributors
3
-
4
- This product includes software and original map artwork developed by Tileflow.dev contributors.
5
-
6
- Third-party materials distributed with a package remain subject to their own license and notice
7
- files. See THIRD_PARTY_NOTICES.md and asset-adjacent license files where present.
8
-
9
- The Tileflow names and marks are not licensed under the Apache License 2.0. See TRADEMARKS.md.
package/TRADEMARKS.md DELETED
@@ -1,10 +0,0 @@
1
- # Tileflow trademarks
2
-
3
- The Apache License, Version 2.0, and the Tileflow generated-output grant do not grant permission to
4
- use the Tileflow name, `tileflow.dev`, logos, service marks, product names, or other Tileflow brand
5
- features except for reasonable and customary use that truthfully describes the origin of the SDK or
6
- compatibility with Tileflow, and for reproducing required notices.
7
-
8
- You may not use Tileflow brand features in a way that suggests sponsorship, endorsement, or an
9
- official Tileflow product when none exists. Third-party names and marks remain the property of their
10
- respective owners.