create-honua-app 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +50 -0
- package/bin/create-honua-app.mjs +7 -0
- package/lib/cli.mjs +149 -0
- package/lib/scaffold.mjs +134 -0
- package/lib/templates.mjs +138 -0
- package/package.json +32 -0
- package/templates/react-ts/.stackblitzrc +4 -0
- package/templates/react-ts/README.md +64 -0
- package/templates/react-ts/_gitignore +6 -0
- package/templates/react-ts/fixtures/features.json +46 -0
- package/templates/react-ts/fixtures/layer.json +34 -0
- package/templates/react-ts/index.html +12 -0
- package/templates/react-ts/package.json +31 -0
- package/templates/react-ts/src/App.tsx +153 -0
- package/templates/react-ts/src/fixture-endpoint.ts +9 -0
- package/templates/react-ts/src/main.tsx +18 -0
- package/templates/react-ts/src/styles.css +149 -0
- package/templates/react-ts/tsconfig.json +15 -0
- package/templates/react-ts/vite.config.ts +50 -0
- package/templates/vanilla-ts/.stackblitzrc +4 -0
- package/templates/vanilla-ts/README.md +60 -0
- package/templates/vanilla-ts/_gitignore +6 -0
- package/templates/vanilla-ts/fixtures/features.json +46 -0
- package/templates/vanilla-ts/fixtures/layer.json +34 -0
- package/templates/vanilla-ts/index.html +37 -0
- package/templates/vanilla-ts/package.json +27 -0
- package/templates/vanilla-ts/src/fixture-endpoint.ts +9 -0
- package/templates/vanilla-ts/src/main.ts +113 -0
- package/templates/vanilla-ts/src/styles.css +149 -0
- package/templates/vanilla-ts/tsconfig.json +14 -0
- package/templates/vanilla-ts/vite.config.ts +50 -0
- package/templates.manifest.json +61 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Honua map app (vanilla TypeScript)
|
|
2
|
+
|
|
3
|
+
A Vite + TypeScript starter for the [Honua JavaScript SDK](https://github.com/honua-io/honua-sdk-js). It runs the
|
|
4
|
+
SDK's protocol-neutral workflow end to end:
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
connect → inspect → explain → query → mount
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Run it
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install
|
|
14
|
+
npm run dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The default lane serves a committed GeoServices fixture from the Vite dev server, so the first map needs no account,
|
|
18
|
+
no API key, and no third-party network call. The same fixture is served by `npm run preview` after `npm run build`.
|
|
19
|
+
|
|
20
|
+
## Point it at live data
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
VITE_HONUA_ENDPOINT=https://your-public-service.example/rest/services/example/FeatureServer/0 npm run dev
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- `VITE_HONUA_ENDPOINT` — an anonymous, CORS-enabled GeoServices FeatureServer layer or OGC API Features landing page.
|
|
27
|
+
- `VITE_HONUA_PROTOCOL` — `auto`, `geoservices-feature-service`, or `ogc-features` (default:
|
|
28
|
+
`geoservices-feature-service`, which matches the fixture).
|
|
29
|
+
|
|
30
|
+
Do not put durable credentials in Vite environment variables: Vite embeds them in public JavaScript. Use an anonymous
|
|
31
|
+
endpoint or a server-side proxy.
|
|
32
|
+
|
|
33
|
+
## What to edit
|
|
34
|
+
|
|
35
|
+
| File | Why |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| `src/main.ts` | The whole workflow: connect, inspect, explain, query, mount. Change the query here. |
|
|
38
|
+
| `src/fixture-endpoint.ts` | The same-origin path the offline fixture is served on. |
|
|
39
|
+
| `vite.config.ts` | The fixture service. Delete it once you point the app at a real endpoint. |
|
|
40
|
+
| `fixtures/` | The committed GeoServices layer description and its query response. |
|
|
41
|
+
|
|
42
|
+
## Dependencies
|
|
43
|
+
|
|
44
|
+
`@honua/sdk-js` and `maplibre-gl` are the two packages this app calls directly. `@bufbuild/protobuf` and the two
|
|
45
|
+
`@connectrpc` packages are the SDK's optional transport peers: they are installed and pinned here so the bundle builds
|
|
46
|
+
without any peer-dependency assembly, and they are what a Honua-server connection uses.
|
|
47
|
+
|
|
48
|
+
This starter pins **MapLibre GL JS 5.24.0**, the major the published `@honua/sdk-js` release accepts as an optional
|
|
49
|
+
peer. MapLibre 6 is supported by the SDK source (`^5.0.0 || ^6.0.0`) and ships in the next release; upgrading to 6.x
|
|
50
|
+
also means configuring MapLibre's ESM worker before the first map is created — under Vite, `setWorkerUrl` with
|
|
51
|
+
`import maplibreWorkerUrl from "maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url"`.
|
|
52
|
+
|
|
53
|
+
## Checks
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm run typecheck
|
|
57
|
+
npm run build
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The fixture data is synthetic demonstration data from the Honua SDK sample fixtures (Apache-2.0).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"objectIdFieldName": "OBJECTID",
|
|
3
|
+
"geometryType": "esriGeometryPolygon",
|
|
4
|
+
"spatialReference": { "wkid": 4326 },
|
|
5
|
+
"fields": [
|
|
6
|
+
{ "name": "OBJECTID", "type": "esriFieldTypeOID", "alias": "OBJECTID" },
|
|
7
|
+
{ "name": "NAME", "type": "esriFieldTypeString", "alias": "Name" },
|
|
8
|
+
{ "name": "STATUS", "type": "esriFieldTypeString", "alias": "Status" },
|
|
9
|
+
{ "name": "CATEGORY", "type": "esriFieldTypeString", "alias": "Category" }
|
|
10
|
+
],
|
|
11
|
+
"features": [
|
|
12
|
+
{
|
|
13
|
+
"attributes": {
|
|
14
|
+
"OBJECTID": 1,
|
|
15
|
+
"NAME": "Civic center service zone",
|
|
16
|
+
"STATUS": "Monitoring",
|
|
17
|
+
"CATEGORY": "Operations"
|
|
18
|
+
},
|
|
19
|
+
"geometry": {
|
|
20
|
+
"rings": [[[-157.866, 21.311], [-157.856, 21.311], [-157.856, 21.302], [-157.866, 21.302], [-157.866, 21.311]]]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"attributes": {
|
|
25
|
+
"OBJECTID": 2,
|
|
26
|
+
"NAME": "Harbor response district",
|
|
27
|
+
"STATUS": "Field review",
|
|
28
|
+
"CATEGORY": "Maritime"
|
|
29
|
+
},
|
|
30
|
+
"geometry": {
|
|
31
|
+
"rings": [[[-157.891, 21.304], [-157.879, 21.304], [-157.879, 21.294], [-157.891, 21.294], [-157.891, 21.304]]]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"attributes": {
|
|
36
|
+
"OBJECTID": 3,
|
|
37
|
+
"NAME": "Kakaako utility corridor",
|
|
38
|
+
"STATUS": "Ready",
|
|
39
|
+
"CATEGORY": "Infrastructure"
|
|
40
|
+
},
|
|
41
|
+
"geometry": {
|
|
42
|
+
"rings": [[[-157.87, 21.296], [-157.856, 21.296], [-157.856, 21.286], [-157.87, 21.286], [-157.87, 21.296]]]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"currentVersion": 11.3,
|
|
3
|
+
"id": 0,
|
|
4
|
+
"name": "Honolulu operations areas",
|
|
5
|
+
"type": "Feature Layer",
|
|
6
|
+
"description": "The same three synthetic First Map polygons exposed by the OGC API Features projection.",
|
|
7
|
+
"copyrightText": "Copyright Honua contributors; synthetic demonstration data.",
|
|
8
|
+
"provenance": {
|
|
9
|
+
"source": "honua-sdk-js test/fixtures/honua-quickstart-demo",
|
|
10
|
+
"retrievedAt": "2026-07-13T00:00:00.000Z"
|
|
11
|
+
},
|
|
12
|
+
"geometryType": "esriGeometryPolygon",
|
|
13
|
+
"objectIdField": "OBJECTID",
|
|
14
|
+
"maxRecordCount": 25,
|
|
15
|
+
"advancedQueryCapabilities": {
|
|
16
|
+
"supportsPagination": true,
|
|
17
|
+
"supportsReturningQueryExtent": false,
|
|
18
|
+
"supportsStatistics": false
|
|
19
|
+
},
|
|
20
|
+
"extent": {
|
|
21
|
+
"xmin": -157.891,
|
|
22
|
+
"ymin": 21.286,
|
|
23
|
+
"xmax": -157.856,
|
|
24
|
+
"ymax": 21.311,
|
|
25
|
+
"spatialReference": { "wkid": 4326 }
|
|
26
|
+
},
|
|
27
|
+
"fields": [
|
|
28
|
+
{ "name": "OBJECTID", "type": "esriFieldTypeOID", "alias": "OBJECTID" },
|
|
29
|
+
{ "name": "NAME", "type": "esriFieldTypeString", "alias": "Name" },
|
|
30
|
+
{ "name": "STATUS", "type": "esriFieldTypeString", "alias": "Status" },
|
|
31
|
+
{ "name": "CATEGORY", "type": "esriFieldTypeString", "alias": "Category" }
|
|
32
|
+
],
|
|
33
|
+
"capabilities": "Query"
|
|
34
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Honua map app</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<main class="app">
|
|
10
|
+
<section class="panel" aria-label="Workflow">
|
|
11
|
+
<p class="eyebrow">Honua JavaScript SDK</p>
|
|
12
|
+
<h1 class="title">connect → inspect → explain → query → mount</h1>
|
|
13
|
+
<p id="status" class="status" role="status" aria-live="polite">Starting the workflow…</p>
|
|
14
|
+
<ol class="stages">
|
|
15
|
+
<li data-stage="connect"><span class="stage-name">Connect</span><span class="stage-state">waiting</span></li>
|
|
16
|
+
<li data-stage="inspect"><span class="stage-name">Inspect</span><span class="stage-state">waiting</span></li>
|
|
17
|
+
<li data-stage="explain"><span class="stage-name">Explain</span><span class="stage-state">waiting</span></li>
|
|
18
|
+
<li data-stage="query"><span class="stage-name">Query</span><span class="stage-state">waiting</span></li>
|
|
19
|
+
<li data-stage="mount"><span class="stage-name">Mount</span><span class="stage-state">waiting</span></li>
|
|
20
|
+
</ol>
|
|
21
|
+
<dl class="facts">
|
|
22
|
+
<div><dt>Data lane</dt><dd id="fact-mode">—</dd></div>
|
|
23
|
+
<div><dt>Endpoint</dt><dd id="fact-endpoint">—</dd></div>
|
|
24
|
+
<div><dt>Protocol</dt><dd id="fact-protocol">—</dd></div>
|
|
25
|
+
<div><dt>Source</dt><dd id="fact-source">—</dd></div>
|
|
26
|
+
<div><dt>Features</dt><dd id="fact-features">—</dd></div>
|
|
27
|
+
</dl>
|
|
28
|
+
<p class="hint">
|
|
29
|
+
Edit <code>src/main.ts</code> to change the query, or set <code>VITE_HONUA_ENDPOINT</code> to run the same
|
|
30
|
+
workflow against a live public service.
|
|
31
|
+
</p>
|
|
32
|
+
</section>
|
|
33
|
+
<div id="map" class="map" role="application" aria-label="Mounted map"></div>
|
|
34
|
+
</main>
|
|
35
|
+
<script type="module" src="/src/main.ts"></script>
|
|
36
|
+
</body>
|
|
37
|
+
</html>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "honua-app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20.19.0"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "vite",
|
|
11
|
+
"build": "vite build",
|
|
12
|
+
"preview": "vite preview",
|
|
13
|
+
"typecheck": "tsc --noEmit"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@bufbuild/protobuf": "2.13.0",
|
|
17
|
+
"@connectrpc/connect": "2.1.2",
|
|
18
|
+
"@connectrpc/connect-web": "2.1.2",
|
|
19
|
+
"@honua/sdk-js": "0.1.2-beta.0",
|
|
20
|
+
"maplibre-gl": "5.24.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "24.13.3",
|
|
24
|
+
"typescript": "5.9.3",
|
|
25
|
+
"vite": "8.2.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Same-origin path the committed fixture service answers on.
|
|
3
|
+
*
|
|
4
|
+
* `vite.config.ts` serves the fixture here in both `npm run dev` and
|
|
5
|
+
* `npm run preview`, and `src/main.ts` connects here whenever no live endpoint
|
|
6
|
+
* is configured. Keeping the path in one module is what makes the default lane
|
|
7
|
+
* work with no account, no key, and no third-party network call.
|
|
8
|
+
*/
|
|
9
|
+
export const FIXTURE_LAYER_PATH = "/fixture/rest/services/honua-demo/FeatureServer/0";
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import "maplibre-gl/dist/maplibre-gl.css";
|
|
2
|
+
|
|
3
|
+
import { createHonua } from "@honua/sdk-js";
|
|
4
|
+
import type { ConnectProtocolHint } from "@honua/sdk-js";
|
|
5
|
+
import { maplibreRenderer } from "@honua/sdk-js/runtime";
|
|
6
|
+
import * as maplibregl from "maplibre-gl";
|
|
7
|
+
|
|
8
|
+
import { FIXTURE_LAYER_PATH } from "./fixture-endpoint.js";
|
|
9
|
+
import "./styles.css";
|
|
10
|
+
|
|
11
|
+
/** Offline basemap: the starter never fetches third-party tiles on its default lane. */
|
|
12
|
+
const BASEMAP_STYLE = {
|
|
13
|
+
version: 8,
|
|
14
|
+
sources: {},
|
|
15
|
+
layers: [{ id: "background", type: "background", paint: { "background-color": "#0e1a17" } }],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const MAP_OPTIONS = { center: [-157.873, 21.2985], zoom: 11.4, attributionControl: false };
|
|
19
|
+
|
|
20
|
+
/** Bounded row budget. Honua never mounts a truncated result silently. */
|
|
21
|
+
const MAX_FEATURES = 250;
|
|
22
|
+
|
|
23
|
+
const configuredEndpoint = import.meta.env.VITE_HONUA_ENDPOINT?.trim();
|
|
24
|
+
const configuredProtocol = import.meta.env.VITE_HONUA_PROTOCOL?.trim();
|
|
25
|
+
|
|
26
|
+
const endpoint =
|
|
27
|
+
configuredEndpoint && configuredEndpoint.length > 0
|
|
28
|
+
? configuredEndpoint
|
|
29
|
+
: new URL(FIXTURE_LAYER_PATH, window.location.origin).toString();
|
|
30
|
+
const protocol = (
|
|
31
|
+
configuredProtocol && configuredProtocol.length > 0 ? configuredProtocol : "geoservices-feature-service"
|
|
32
|
+
) as ConnectProtocolHint;
|
|
33
|
+
const dataLane = configuredEndpoint && configuredEndpoint.length > 0 ? "Live endpoint" : "Committed fixture";
|
|
34
|
+
|
|
35
|
+
function element(id: string): HTMLElement {
|
|
36
|
+
const found = document.getElementById(id);
|
|
37
|
+
if (!found) throw new Error(`Missing #${id} element`);
|
|
38
|
+
return found;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function fact(name: string, value: string): void {
|
|
42
|
+
element(`fact-${name}`).textContent = value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function stage(name: string, state: "running" | "done" | "failed", detail: string): void {
|
|
46
|
+
const item = document.querySelector<HTMLElement>(`[data-stage="${name}"]`);
|
|
47
|
+
if (!item) return;
|
|
48
|
+
item.dataset["state"] = state;
|
|
49
|
+
const label = item.querySelector<HTMLElement>(".stage-state");
|
|
50
|
+
if (label) label.textContent = detail;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function run(): Promise<void> {
|
|
54
|
+
fact("mode", dataLane);
|
|
55
|
+
fact("endpoint", endpoint);
|
|
56
|
+
|
|
57
|
+
const honua = createHonua();
|
|
58
|
+
try {
|
|
59
|
+
stage("connect", "running", "connecting");
|
|
60
|
+
const connection = await honua.connect(
|
|
61
|
+
{ url: endpoint, protocol },
|
|
62
|
+
{ authorizationScopeFingerprint: "anonymous-public" },
|
|
63
|
+
);
|
|
64
|
+
stage("connect", "done", "connected");
|
|
65
|
+
|
|
66
|
+
stage("inspect", "running", "discovering");
|
|
67
|
+
const inspection = await connection.inspect();
|
|
68
|
+
const sourceId = inspection.defaultSourceId ?? inspection.sources[0]?.descriptor.id;
|
|
69
|
+
if (!sourceId) throw new Error("The endpoint advertised no queryable source.");
|
|
70
|
+
fact("protocol", inspection.protocol);
|
|
71
|
+
fact("source", sourceId);
|
|
72
|
+
stage("inspect", "done", `${inspection.sources.length} source(s)`);
|
|
73
|
+
|
|
74
|
+
stage("explain", "running", "planning");
|
|
75
|
+
const plan = await connection.explain(
|
|
76
|
+
{
|
|
77
|
+
returnGeometry: true,
|
|
78
|
+
pagination: { limit: MAX_FEATURES },
|
|
79
|
+
...(inspection.protocol === "geoservices-feature-service" ? { outSr: 4326 } : {}),
|
|
80
|
+
},
|
|
81
|
+
{ sourceId },
|
|
82
|
+
);
|
|
83
|
+
stage("explain", "done", "plan accepted");
|
|
84
|
+
|
|
85
|
+
stage("query", "running", "executing");
|
|
86
|
+
const result = await connection.query(plan);
|
|
87
|
+
fact("features", `${result.features.length}${result.exceededTransferLimit ? " (truncated)" : ""}`);
|
|
88
|
+
stage("query", "done", `${result.features.length} feature(s)`);
|
|
89
|
+
|
|
90
|
+
stage("mount", "running", "rendering");
|
|
91
|
+
// A selector or element target makes the SDK own the map's lifecycle; pass
|
|
92
|
+
// an existing `maplibregl.Map` instead to keep ownership in the app.
|
|
93
|
+
const mounted = await connection.mount(element("map"), {
|
|
94
|
+
renderer: maplibreRenderer(maplibregl),
|
|
95
|
+
query: plan,
|
|
96
|
+
sourceId,
|
|
97
|
+
style: BASEMAP_STYLE,
|
|
98
|
+
rendererOptions: { mapOptions: MAP_OPTIONS },
|
|
99
|
+
});
|
|
100
|
+
await mounted.ready;
|
|
101
|
+
stage("mount", "done", "mounted");
|
|
102
|
+
element("status").textContent = "The accepted plan is mounted. Every stage above ran through the public SDK API.";
|
|
103
|
+
} catch (error) {
|
|
104
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
105
|
+
element("status").textContent = `The workflow stopped: ${message}`;
|
|
106
|
+
for (const item of document.querySelectorAll<HTMLElement>("[data-stage]")) {
|
|
107
|
+
if (item.dataset["state"] === "running") stage(item.dataset["stage"] ?? "", "failed", "failed");
|
|
108
|
+
}
|
|
109
|
+
await honua.dispose();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
void run();
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--surface: #f6f7f5;
|
|
3
|
+
--panel: #ffffff;
|
|
4
|
+
--ink: #10201b;
|
|
5
|
+
--muted: #5c6b66;
|
|
6
|
+
--line: #d9e0dc;
|
|
7
|
+
--accent: #0f766e;
|
|
8
|
+
color-scheme: light dark;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@media (prefers-color-scheme: dark) {
|
|
12
|
+
:root {
|
|
13
|
+
--surface: #0b1210;
|
|
14
|
+
--panel: #111a17;
|
|
15
|
+
--ink: #e8f0ec;
|
|
16
|
+
--muted: #93a49e;
|
|
17
|
+
--line: #23322c;
|
|
18
|
+
--accent: #5eead4;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
* {
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
body {
|
|
27
|
+
margin: 0;
|
|
28
|
+
background: var(--surface);
|
|
29
|
+
color: var(--ink);
|
|
30
|
+
font: 15px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.app {
|
|
34
|
+
display: grid;
|
|
35
|
+
grid-template-columns: minmax(280px, 360px) 1fr;
|
|
36
|
+
gap: 16px;
|
|
37
|
+
padding: 16px;
|
|
38
|
+
height: 100vh;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@media (max-width: 820px) {
|
|
42
|
+
.app {
|
|
43
|
+
grid-template-columns: 1fr;
|
|
44
|
+
grid-template-rows: auto minmax(320px, 1fr);
|
|
45
|
+
height: auto;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.panel {
|
|
50
|
+
background: var(--panel);
|
|
51
|
+
border: 1px solid var(--line);
|
|
52
|
+
border-radius: 12px;
|
|
53
|
+
padding: 20px;
|
|
54
|
+
overflow-y: auto;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.eyebrow {
|
|
58
|
+
margin: 0;
|
|
59
|
+
color: var(--accent);
|
|
60
|
+
font-size: 12px;
|
|
61
|
+
font-weight: 600;
|
|
62
|
+
letter-spacing: 0.08em;
|
|
63
|
+
text-transform: uppercase;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.title {
|
|
67
|
+
margin: 6px 0 12px;
|
|
68
|
+
font-size: 18px;
|
|
69
|
+
line-height: 1.3;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.status {
|
|
73
|
+
margin: 0 0 16px;
|
|
74
|
+
color: var(--muted);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.stages {
|
|
78
|
+
list-style: none;
|
|
79
|
+
margin: 0 0 20px;
|
|
80
|
+
padding: 0;
|
|
81
|
+
display: grid;
|
|
82
|
+
gap: 6px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.stages li {
|
|
86
|
+
display: flex;
|
|
87
|
+
justify-content: space-between;
|
|
88
|
+
gap: 12px;
|
|
89
|
+
padding: 8px 10px;
|
|
90
|
+
border: 1px solid var(--line);
|
|
91
|
+
border-radius: 8px;
|
|
92
|
+
font-size: 13px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.stages li[data-state="running"] {
|
|
96
|
+
border-color: var(--accent);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.stages li[data-state="done"] .stage-state {
|
|
100
|
+
color: var(--accent);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.stages li[data-state="failed"] .stage-state {
|
|
104
|
+
color: #dc2626;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.stage-state {
|
|
108
|
+
color: var(--muted);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.facts {
|
|
112
|
+
display: grid;
|
|
113
|
+
gap: 8px;
|
|
114
|
+
margin: 0 0 16px;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.facts div {
|
|
118
|
+
display: grid;
|
|
119
|
+
grid-template-columns: 96px 1fr;
|
|
120
|
+
gap: 10px;
|
|
121
|
+
font-size: 13px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.facts dt {
|
|
125
|
+
color: var(--muted);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.facts dd {
|
|
129
|
+
margin: 0;
|
|
130
|
+
overflow-wrap: anywhere;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.hint {
|
|
134
|
+
margin: 0;
|
|
135
|
+
color: var(--muted);
|
|
136
|
+
font-size: 13px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
code {
|
|
140
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
|
141
|
+
font-size: 12px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.map {
|
|
145
|
+
min-height: 360px;
|
|
146
|
+
border: 1px solid var(--line);
|
|
147
|
+
border-radius: 12px;
|
|
148
|
+
overflow: hidden;
|
|
149
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"verbatimModuleSyntax": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"types": ["vite/client", "node"]
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*.ts", "vite.config.ts"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { type Plugin, defineConfig } from "vite";
|
|
6
|
+
|
|
7
|
+
import { FIXTURE_LAYER_PATH } from "./src/fixture-endpoint.js";
|
|
8
|
+
|
|
9
|
+
function fixtureDocument(name: string): string {
|
|
10
|
+
return readFileSync(fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)), "utf8");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Deterministic, offline GeoServices FeatureServer fixture.
|
|
15
|
+
*
|
|
16
|
+
* The starter's green path must never depend on a third-party endpoint, so the
|
|
17
|
+
* dev and preview servers answer the two routes the SDK's GeoServices adapter
|
|
18
|
+
* needs: the layer description and its bounded query. Point
|
|
19
|
+
* `VITE_HONUA_ENDPOINT` at a real service to run the same code against live data.
|
|
20
|
+
*/
|
|
21
|
+
function honuaFixtureService(): Plugin {
|
|
22
|
+
const layer = fixtureDocument("layer.json");
|
|
23
|
+
const features = fixtureDocument("features.json");
|
|
24
|
+
const middleware = (request: IncomingMessage, response: ServerResponse, next: () => void): void => {
|
|
25
|
+
const pathname = new URL(request.url ?? "/", "http://localhost").pathname;
|
|
26
|
+
let body: string | undefined;
|
|
27
|
+
if (pathname === FIXTURE_LAYER_PATH) body = layer;
|
|
28
|
+
else if (pathname === `${FIXTURE_LAYER_PATH}/query`) body = features;
|
|
29
|
+
if (body === undefined) {
|
|
30
|
+
next();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
34
|
+
response.setHeader("cache-control", "no-store");
|
|
35
|
+
response.end(body);
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
name: "honua-fixture-service",
|
|
39
|
+
configureServer(server) {
|
|
40
|
+
server.middlewares.use(middleware);
|
|
41
|
+
},
|
|
42
|
+
configurePreviewServer(server) {
|
|
43
|
+
server.middlewares.use(middleware);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default defineConfig({
|
|
49
|
+
plugins: [honuaFixtureService()],
|
|
50
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": "honua.sdk.create-honua-app-templates.v1",
|
|
3
|
+
"schemaVersion": 1,
|
|
4
|
+
"sdk": {
|
|
5
|
+
"package": "@honua/sdk-js",
|
|
6
|
+
"version": "0.1.2-beta.0"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"owner": "honua-io",
|
|
10
|
+
"name": "honua-sdk-js",
|
|
11
|
+
"branch": "trunk"
|
|
12
|
+
},
|
|
13
|
+
"playgroundProviders": [
|
|
14
|
+
{
|
|
15
|
+
"id": "stackblitz",
|
|
16
|
+
"title": "StackBlitz",
|
|
17
|
+
"urlTemplate": "https://stackblitz.com/github/{owner}/{repository}/tree/{branch}/{path}"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "codesandbox",
|
|
21
|
+
"title": "CodeSandbox",
|
|
22
|
+
"urlTemplate": "https://codesandbox.io/s/github/{owner}/{repository}/tree/{branch}/{path}"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"templates": [
|
|
26
|
+
{
|
|
27
|
+
"id": "vanilla-ts",
|
|
28
|
+
"title": "Vanilla TypeScript",
|
|
29
|
+
"summary": "Vite + TypeScript app that connects to an endpoint, explains a bounded query, and mounts the accepted plan on MapLibre.",
|
|
30
|
+
"path": "packages/create-honua-app/templates/vanilla-ts",
|
|
31
|
+
"entryFile": "src/main.ts",
|
|
32
|
+
"dataLane": "fixture",
|
|
33
|
+
"default": true,
|
|
34
|
+
"dependencies": [
|
|
35
|
+
"@honua/sdk-js",
|
|
36
|
+
"maplibre-gl",
|
|
37
|
+
"@bufbuild/protobuf",
|
|
38
|
+
"@connectrpc/connect",
|
|
39
|
+
"@connectrpc/connect-web"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "react-ts",
|
|
44
|
+
"title": "React TypeScript",
|
|
45
|
+
"summary": "Vite + React + TypeScript app that mounts the same accepted plan through @honua/sdk-js/react hooks on an app-owned MapLibre map.",
|
|
46
|
+
"path": "packages/create-honua-app/templates/react-ts",
|
|
47
|
+
"entryFile": "src/App.tsx",
|
|
48
|
+
"dataLane": "fixture",
|
|
49
|
+
"default": false,
|
|
50
|
+
"dependencies": [
|
|
51
|
+
"@honua/sdk-js",
|
|
52
|
+
"maplibre-gl",
|
|
53
|
+
"react",
|
|
54
|
+
"react-dom",
|
|
55
|
+
"@bufbuild/protobuf",
|
|
56
|
+
"@connectrpc/connect",
|
|
57
|
+
"@connectrpc/connect-web"
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|