tileflow 0.1.0-alpha.15 → 0.1.0-alpha.16
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 +7 -3
- package/dist/index.js +482 -78
- package/docs/hosted-authentication-and-deploy.md +35 -2
- package/docs/native-artifacts.md +2 -2
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -222,13 +222,17 @@ browser bundle, copied prompt, or command-line argument.
|
|
|
222
222
|
```sh
|
|
223
223
|
npx tileflow login
|
|
224
224
|
npx tileflow whoami --json
|
|
225
|
+
npx tileflow deploy --origin https://www.example.com
|
|
225
226
|
npx tileflow deploy --map-id map_AbCdEfGhIjKlMnOp
|
|
226
227
|
npx tileflow status --map-id map_AbCdEfGhIjKlMnOp
|
|
227
228
|
```
|
|
228
229
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
Without `--map-id`, a logged-in `deploy` first creates the Map in the Team you administer (it asks,
|
|
231
|
+
or takes `--team @team`, when several qualify) with the browser access you choose: exact origins
|
|
232
|
+
with `--origin`, or `--any-website`. It prints the Map ID to use for later deploys;
|
|
233
|
+
`tileflow maps create` creates the Map without deploying. The Map ID is a public identifier, not a
|
|
234
|
+
credential or the portable `id` in your config. CI always deploys an existing Map with a deploy key
|
|
235
|
+
for that destination. `logout` removes the saved personal session.
|
|
232
236
|
|
|
233
237
|
Deploy publishes the compiled concrete-theme family and managed icon assets; it does not ask the
|
|
234
238
|
server to compile TypeScript. Repeating an unchanged deployment reuses its version. Build writers
|
package/dist/index.js
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { serve } from "@hono/node-server";
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
-
import { createHash as createHash4, randomBytes as
|
|
6
|
+
import { createHash as createHash4, randomBytes as randomBytes3 } from "crypto";
|
|
7
7
|
import { existsSync, readFileSync, realpathSync } from "fs";
|
|
8
8
|
import { readFile as readFile7, writeFile as writeFile2 } from "fs/promises";
|
|
9
9
|
import { hostname } from "os";
|
|
10
10
|
import { dirname as dirname13, isAbsolute as isAbsolute5, relative as relative6, resolve as resolve12, sep as sep5 } from "path";
|
|
11
|
-
import { createInterface } from "readline/promises";
|
|
12
|
-
import
|
|
11
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
12
|
+
import pc9 from "picocolors";
|
|
13
13
|
import { createLocalCoordinates, setupCoordinates } from "@tileflow/coordinates-runtime";
|
|
14
14
|
import {
|
|
15
15
|
auditTileflowMapThemeValues as auditTileflowMapThemeValues2,
|
|
@@ -4447,6 +4447,88 @@ async function listAccountTeams(session, options = {}) {
|
|
|
4447
4447
|
}
|
|
4448
4448
|
return { ok: true, teams };
|
|
4449
4449
|
}
|
|
4450
|
+
async function listMapSetupTeams(session, options = {}) {
|
|
4451
|
+
let response;
|
|
4452
|
+
try {
|
|
4453
|
+
response = await requestHostedJson(
|
|
4454
|
+
session.apiOrigin,
|
|
4455
|
+
"/v1/cli/map-setup/teams",
|
|
4456
|
+
{ headers: authorizationHeaders(session.accountSession) },
|
|
4457
|
+
options
|
|
4458
|
+
);
|
|
4459
|
+
} catch (error) {
|
|
4460
|
+
return { error: safeTransportError(error, "Team discovery failed."), ok: false };
|
|
4461
|
+
}
|
|
4462
|
+
if (!response.ok) {
|
|
4463
|
+
return {
|
|
4464
|
+
error: response.status === 401 ? rejectedAccountSessionMessage : `Team discovery failed (${response.status}).`,
|
|
4465
|
+
ok: false
|
|
4466
|
+
};
|
|
4467
|
+
}
|
|
4468
|
+
const body = asRecord(response.body);
|
|
4469
|
+
if (!response.json || body.schemaVersion !== 1 || !Array.isArray(body.teams) || !body.teams.every(isMapSetupTeam)) {
|
|
4470
|
+
return { error: "Team discovery returned an invalid response.", ok: false };
|
|
4471
|
+
}
|
|
4472
|
+
return { ok: true, teams: body.teams };
|
|
4473
|
+
}
|
|
4474
|
+
async function createManagedMapSetup(session, input, options = {}) {
|
|
4475
|
+
let response;
|
|
4476
|
+
try {
|
|
4477
|
+
response = await requestHostedJson(
|
|
4478
|
+
session.apiOrigin,
|
|
4479
|
+
"/v1/cli/maps",
|
|
4480
|
+
{
|
|
4481
|
+
...jsonRequest("POST", input),
|
|
4482
|
+
headers: {
|
|
4483
|
+
...authorizationHeaders(session.accountSession),
|
|
4484
|
+
"Content-Type": "application/json"
|
|
4485
|
+
}
|
|
4486
|
+
},
|
|
4487
|
+
options
|
|
4488
|
+
);
|
|
4489
|
+
} catch (error) {
|
|
4490
|
+
return {
|
|
4491
|
+
code: "setup_failed",
|
|
4492
|
+
error: safeTransportError(error, "Map setup request failed."),
|
|
4493
|
+
ok: false
|
|
4494
|
+
};
|
|
4495
|
+
}
|
|
4496
|
+
const body = asRecord(response.body);
|
|
4497
|
+
if (!response.ok) {
|
|
4498
|
+
if (response.status === 401) {
|
|
4499
|
+
return { code: "account_session_rejected", error: rejectedAccountSessionMessage, ok: false };
|
|
4500
|
+
}
|
|
4501
|
+
const code = mapSetupFailureMessages[body.code] ? body.code : null;
|
|
4502
|
+
return code ? { code, error: mapSetupFailureMessages[code], ok: false } : { code: "setup_failed", error: `Map setup failed (${response.status}).`, ok: false };
|
|
4503
|
+
}
|
|
4504
|
+
const map = asRecord(body.map);
|
|
4505
|
+
const team = asRecord(body.team);
|
|
4506
|
+
const delivery = asRecord(body.delivery);
|
|
4507
|
+
if (!response.json || body.schemaVersion !== 1 || typeof map.id !== "string" || !/^map_[A-Za-z0-9_-]{16}$/u.test(map.id) || map.name !== null && typeof map.name !== "string" || map.state !== "setup_pending" && map.state !== "active" || typeof team.name !== "string" || `@${String(team.slug)}` !== input.team || !Array.isArray(delivery.allowedOrigins) || !delivery.allowedOrigins.every((origin) => typeof origin === "string") || delivery.allowedOrigins.length !== input.allowedOrigins.length) {
|
|
4508
|
+
return { code: "setup_failed", error: "Map setup returned an invalid response.", ok: false };
|
|
4509
|
+
}
|
|
4510
|
+
return {
|
|
4511
|
+
ok: true,
|
|
4512
|
+
value: {
|
|
4513
|
+
allowedOrigins: delivery.allowedOrigins,
|
|
4514
|
+
map: { id: map.id, name: map.name, state: map.state },
|
|
4515
|
+
team: { name: team.name, slug: team.slug }
|
|
4516
|
+
}
|
|
4517
|
+
};
|
|
4518
|
+
}
|
|
4519
|
+
var mapSetupFailureMessages = {
|
|
4520
|
+
invalid_request: "Tileflow rejected the Map setup request.",
|
|
4521
|
+
map_limit: "This Team has reached its Map limit. Archive or delete a Map, or change the plan, in the Tileflow dashboard.",
|
|
4522
|
+
permission_required: "Only a Team Admin can create Maps.",
|
|
4523
|
+
setup_conflict: "This setup key was already used with another Team or browser access choice. Omit --setup-key to start a new setup.",
|
|
4524
|
+
team_not_found: "No Team you administer matches this Team."
|
|
4525
|
+
};
|
|
4526
|
+
function isMapSetupTeam(value) {
|
|
4527
|
+
const team = asRecord(value);
|
|
4528
|
+
return typeof team.eligible === "boolean" && typeof team.name === "string" && typeof team.slug === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(team.slug) && (team.unavailableReason === void 0 || team.unavailableReason === "map_limit") && Object.keys(team).every(
|
|
4529
|
+
(key) => ["eligible", "name", "slug", "unavailableReason"].includes(key)
|
|
4530
|
+
);
|
|
4531
|
+
}
|
|
4450
4532
|
async function publishHostedStyle(api, body, responseLabel, options = {}) {
|
|
4451
4533
|
const response = await requestHostedJson(
|
|
4452
4534
|
api.apiUrl,
|
|
@@ -6917,10 +6999,299 @@ async function readCurrentPins(contents) {
|
|
|
6917
6999
|
return (await parseTileflowIconsLockfile(contents)).sets;
|
|
6918
7000
|
}
|
|
6919
7001
|
|
|
7002
|
+
// src/map-commands.ts
|
|
7003
|
+
import pc5 from "picocolors";
|
|
7004
|
+
|
|
7005
|
+
// src/map-setup.ts
|
|
7006
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
7007
|
+
import { createInterface } from "readline/promises";
|
|
7008
|
+
import pc4 from "picocolors";
|
|
7009
|
+
var maximumOrigins2 = 20;
|
|
7010
|
+
var exactHost = /^(?:[a-z0-9-]+(?:\.[a-z0-9-]+)*|\[[0-9a-f:.]+\])$/u;
|
|
7011
|
+
var teamReference = /^@([A-Za-z0-9][A-Za-z0-9._-]{0,127})$/u;
|
|
7012
|
+
var setupKeyPattern = /^[\x21-\x7e]{8,128}$/u;
|
|
7013
|
+
function resolveMapSetupTeam(teams, requestedSlug) {
|
|
7014
|
+
if (requestedSlug !== void 0) {
|
|
7015
|
+
const team = teams.find(({ slug }) => slug === requestedSlug);
|
|
7016
|
+
if (!team) return { kind: "not_found" };
|
|
7017
|
+
return team.eligible ? { kind: "selected", team } : { kind: "map_limit" };
|
|
7018
|
+
}
|
|
7019
|
+
const eligible = teams.filter((team) => team.eligible);
|
|
7020
|
+
if (eligible.length === 1) return { kind: "selected", team: eligible[0] };
|
|
7021
|
+
if (eligible.length > 1) return { kind: "choose", teams: eligible };
|
|
7022
|
+
return teams.some((team) => team.unavailableReason === "map_limit") ? { kind: "map_limit" } : { kind: "none" };
|
|
7023
|
+
}
|
|
7024
|
+
function normalizeBrowserOrigin(value) {
|
|
7025
|
+
let url;
|
|
7026
|
+
try {
|
|
7027
|
+
url = new URL(value);
|
|
7028
|
+
} catch {
|
|
7029
|
+
return null;
|
|
7030
|
+
}
|
|
7031
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.pathname !== "/" || url.search || url.hash || !exactHost.test(url.hostname) || value.toLowerCase() !== url.origin.toLowerCase()) {
|
|
7032
|
+
return null;
|
|
7033
|
+
}
|
|
7034
|
+
return url.origin.toLowerCase();
|
|
7035
|
+
}
|
|
7036
|
+
function parseBrowserAccess(options) {
|
|
7037
|
+
const values = options.origin ?? [];
|
|
7038
|
+
if (options.anyWebsite && values.length > 0) {
|
|
7039
|
+
return { error: "Choose either --any-website or --origin, not both.", ok: false };
|
|
7040
|
+
}
|
|
7041
|
+
if (options.anyWebsite) return { ok: true, value: { kind: "any" } };
|
|
7042
|
+
if (values.length === 0) return { ok: true, value: null };
|
|
7043
|
+
return parseOriginList(values);
|
|
7044
|
+
}
|
|
7045
|
+
function parseOriginList(values) {
|
|
7046
|
+
const origins = /* @__PURE__ */ new Set();
|
|
7047
|
+
for (const value of values) {
|
|
7048
|
+
const origin = normalizeBrowserOrigin(value.trim());
|
|
7049
|
+
if (!origin) {
|
|
7050
|
+
return {
|
|
7051
|
+
error: `${JSON.stringify(value)} is not an exact website origin such as https://example.com (no path or wildcard).`,
|
|
7052
|
+
ok: false
|
|
7053
|
+
};
|
|
7054
|
+
}
|
|
7055
|
+
origins.add(origin);
|
|
7056
|
+
}
|
|
7057
|
+
if (origins.size === 0 || origins.size > maximumOrigins2) {
|
|
7058
|
+
return { error: `Enter between 1 and ${maximumOrigins2} website origins.`, ok: false };
|
|
7059
|
+
}
|
|
7060
|
+
return { ok: true, value: { kind: "selected", origins: [...origins].sort() } };
|
|
7061
|
+
}
|
|
7062
|
+
function checkMapSetupOptions(options) {
|
|
7063
|
+
if (options.team !== void 0 && !teamReference.test(options.team)) {
|
|
7064
|
+
return { code: "invalid_team", error: "Team must use @team syntax.", ok: false };
|
|
7065
|
+
}
|
|
7066
|
+
if (options.setupKey !== void 0 && !setupKeyPattern.test(options.setupKey)) {
|
|
7067
|
+
return {
|
|
7068
|
+
code: "invalid_setup_key",
|
|
7069
|
+
error: "Setup key must be 8 to 128 visible ASCII characters without spaces.",
|
|
7070
|
+
ok: false
|
|
7071
|
+
};
|
|
7072
|
+
}
|
|
7073
|
+
if (options.name !== void 0 && (options.name.length < 1 || options.name.length > 200 || options.name !== options.name.trim() || /\p{Cc}/u.test(options.name))) {
|
|
7074
|
+
return { code: "invalid_name", error: "Map name must be 1 to 200 characters.", ok: false };
|
|
7075
|
+
}
|
|
7076
|
+
const access = parseBrowserAccess(options);
|
|
7077
|
+
return access.ok ? { access: access.value, ok: true } : { code: "invalid_browser_access", error: access.error, ok: false };
|
|
7078
|
+
}
|
|
7079
|
+
async function setUpManagedMap(session, options, dependencies) {
|
|
7080
|
+
const checked = checkMapSetupOptions(options);
|
|
7081
|
+
if (!checked.ok) return checked;
|
|
7082
|
+
const discovered = await listMapSetupTeams(session, dependencies.request);
|
|
7083
|
+
if (!discovered.ok) {
|
|
7084
|
+
return { code: "team_discovery_failed", error: discovered.error, ok: false };
|
|
7085
|
+
}
|
|
7086
|
+
const resolution = resolveMapSetupTeam(
|
|
7087
|
+
discovered.teams,
|
|
7088
|
+
options.team ? teamReference.exec(options.team)[1] : void 0
|
|
7089
|
+
);
|
|
7090
|
+
let team = null;
|
|
7091
|
+
if (resolution.kind === "selected") team = resolution.team;
|
|
7092
|
+
else if (resolution.kind === "choose") {
|
|
7093
|
+
team = dependencies.prompts ? await dependencies.prompts.chooseTeam(resolution.teams) : null;
|
|
7094
|
+
if (!team) {
|
|
7095
|
+
const choices = resolution.teams.map(({ slug }) => `@${slug}`);
|
|
7096
|
+
return {
|
|
7097
|
+
code: "team_ambiguous",
|
|
7098
|
+
error: `Several Teams can own this Map. Choose one with --team: ${choices.join(", ")}.`,
|
|
7099
|
+
ok: false,
|
|
7100
|
+
options: choices
|
|
7101
|
+
};
|
|
7102
|
+
}
|
|
7103
|
+
} else if (resolution.kind === "map_limit") {
|
|
7104
|
+
return {
|
|
7105
|
+
code: "map_limit",
|
|
7106
|
+
error: options.team ? `${options.team} has reached its Map limit. Archive or delete a Map, or change the plan, in the Tileflow dashboard.` : "Every Team you administer has reached its Map limit. Archive or delete a Map, or change the plan, in the Tileflow dashboard.",
|
|
7107
|
+
ok: false
|
|
7108
|
+
};
|
|
7109
|
+
} else if (resolution.kind === "not_found") {
|
|
7110
|
+
return { code: "team_not_found", error: `You do not administer ${options.team}.`, ok: false };
|
|
7111
|
+
} else {
|
|
7112
|
+
return {
|
|
7113
|
+
code: "no_eligible_team",
|
|
7114
|
+
error: "This account does not administer a Team. Ask a Team Admin to create the Map.",
|
|
7115
|
+
ok: false
|
|
7116
|
+
};
|
|
7117
|
+
}
|
|
7118
|
+
const access = checked.access ?? (dependencies.prompts ? await dependencies.prompts.chooseAccess() : null);
|
|
7119
|
+
if (!access) {
|
|
7120
|
+
return {
|
|
7121
|
+
code: "browser_access_required",
|
|
7122
|
+
error: "Choose which websites may load this Map: --origin <origin> (repeatable) for exact sites, or --any-website.",
|
|
7123
|
+
ok: false
|
|
7124
|
+
};
|
|
7125
|
+
}
|
|
7126
|
+
const setupKey = options.setupKey ?? `cli-${randomBytes2(16).toString("hex")}`;
|
|
7127
|
+
const created = await createManagedMapSetup(
|
|
7128
|
+
session,
|
|
7129
|
+
{
|
|
7130
|
+
allowedOrigins: access.kind === "any" ? [] : access.origins,
|
|
7131
|
+
idempotencyKey: setupKey,
|
|
7132
|
+
...options.name ? { name: options.name } : {},
|
|
7133
|
+
team: `@${team.slug}`
|
|
7134
|
+
},
|
|
7135
|
+
dependencies.request
|
|
7136
|
+
);
|
|
7137
|
+
return created.ok ? { ok: true, setupKey, value: created.value } : created;
|
|
7138
|
+
}
|
|
7139
|
+
function describeBrowserAccess(allowedOrigins) {
|
|
7140
|
+
return allowedOrigins.length === 0 ? "Any website" : allowedOrigins.join(", ");
|
|
7141
|
+
}
|
|
7142
|
+
function createTerminalPrompts(json2 = false) {
|
|
7143
|
+
if (json2 || !process.stdin.isTTY || !process.stdout.isTTY) return null;
|
|
7144
|
+
return {
|
|
7145
|
+
async chooseTeam(teams) {
|
|
7146
|
+
console.log(`${pc4.cyan("?")} Which Team should own the new Map?`);
|
|
7147
|
+
teams.forEach(
|
|
7148
|
+
(team, index) => console.log(` ${index + 1}) ${team.name} ${pc4.gray(`@${team.slug}`)}`)
|
|
7149
|
+
);
|
|
7150
|
+
const answer = await ask("Team number", (value) => {
|
|
7151
|
+
const index = Number(value);
|
|
7152
|
+
return Number.isInteger(index) && index >= 1 && index <= teams.length ? teams[index - 1] : null;
|
|
7153
|
+
});
|
|
7154
|
+
return answer;
|
|
7155
|
+
},
|
|
7156
|
+
async chooseAccess() {
|
|
7157
|
+
console.log(`${pc4.cyan("?")} Which websites may load this Map in a browser?`);
|
|
7158
|
+
console.log(" 1) Only selected websites (recommended)");
|
|
7159
|
+
console.log(" 2) Any website");
|
|
7160
|
+
const mode = await ask(
|
|
7161
|
+
"Choice",
|
|
7162
|
+
(value) => value === "1" ? "selected" : value === "2" ? "any" : null
|
|
7163
|
+
);
|
|
7164
|
+
if (mode === null) return null;
|
|
7165
|
+
if (mode === "any") return { kind: "any" };
|
|
7166
|
+
return ask("Website origins, separated by spaces (https://example.com)", (value) => {
|
|
7167
|
+
const parsed = parseOriginList(value.split(/[\s,]+/u).filter(Boolean));
|
|
7168
|
+
if (!parsed.ok) console.log(` ${pc4.yellow(parsed.error)}`);
|
|
7169
|
+
return parsed.ok ? parsed.value : null;
|
|
7170
|
+
});
|
|
7171
|
+
}
|
|
7172
|
+
};
|
|
7173
|
+
}
|
|
7174
|
+
async function ask(question, parse3) {
|
|
7175
|
+
const readline = createInterface({ input: process.stdin, output: process.stdout });
|
|
7176
|
+
try {
|
|
7177
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
7178
|
+
const value = parse3((await readline.question(` ${question}: `)).trim());
|
|
7179
|
+
if (value !== null) return value;
|
|
7180
|
+
}
|
|
7181
|
+
return null;
|
|
7182
|
+
} finally {
|
|
7183
|
+
readline.close();
|
|
7184
|
+
}
|
|
7185
|
+
}
|
|
7186
|
+
|
|
7187
|
+
// src/map-commands.ts
|
|
7188
|
+
function registerMapCommands(program2, dependencies) {
|
|
7189
|
+
const maps = program2.command("maps").description("Manage Tileflow managed Maps");
|
|
7190
|
+
addMapSetupOptions(
|
|
7191
|
+
maps.command("create").description("Create a managed Map for this account, ready for tileflow deploy --map-id").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--name <name>", "Map name shown in the dashboard").option("--json", "print deterministic schema-version-1 JSON")
|
|
7192
|
+
).action(async (options) => {
|
|
7193
|
+
const session = await loadSession(options, dependencies);
|
|
7194
|
+
if (!session) return;
|
|
7195
|
+
const result = await setUpManagedMap(session, options, {
|
|
7196
|
+
prompts: createTerminalPrompts(options.json)
|
|
7197
|
+
});
|
|
7198
|
+
if (!result.ok) {
|
|
7199
|
+
emitFailure(
|
|
7200
|
+
options.json,
|
|
7201
|
+
result.code,
|
|
7202
|
+
result.error,
|
|
7203
|
+
result.options ? { options: result.options } : {}
|
|
7204
|
+
);
|
|
7205
|
+
return;
|
|
7206
|
+
}
|
|
7207
|
+
const next = `tileflow deploy --map-id ${result.value.map.id}`;
|
|
7208
|
+
if (options.json) {
|
|
7209
|
+
emitJson({
|
|
7210
|
+
command: "maps create",
|
|
7211
|
+
delivery: { allowedOrigins: result.value.allowedOrigins },
|
|
7212
|
+
map: result.value.map,
|
|
7213
|
+
next,
|
|
7214
|
+
ok: true,
|
|
7215
|
+
schemaVersion: 1,
|
|
7216
|
+
setupKey: result.setupKey,
|
|
7217
|
+
team: result.value.team
|
|
7218
|
+
});
|
|
7219
|
+
return;
|
|
7220
|
+
}
|
|
7221
|
+
printCreatedMap(result.value);
|
|
7222
|
+
console.log(`
|
|
7223
|
+
${pc5.bold("Next step")}`);
|
|
7224
|
+
console.log(` ${pc5.cyan("\u2022")} Publish the repository map with ${pc5.cyan(next)}.`);
|
|
7225
|
+
});
|
|
7226
|
+
}
|
|
7227
|
+
function addMapSetupOptions(command2) {
|
|
7228
|
+
return command2.option("--team <team>", "Team that owns a new Map, as @team").option(
|
|
7229
|
+
"--origin <origin>",
|
|
7230
|
+
"exact website origin that may load a new Map (repeatable)",
|
|
7231
|
+
(value, previous = []) => [...previous, value]
|
|
7232
|
+
).option("--any-website", "let any website load a new Map").option("--setup-key <key>", "repeat an interrupted Map creation with the key it printed");
|
|
7233
|
+
}
|
|
7234
|
+
function hasMapSetupOptions(options) {
|
|
7235
|
+
return options.team !== void 0 || (options.origin?.length ?? 0) > 0 || options.anyWebsite === true || options.setupKey !== void 0;
|
|
7236
|
+
}
|
|
7237
|
+
async function createMapForDeploy(options, dependencies) {
|
|
7238
|
+
const session = await loadSession(options, dependencies);
|
|
7239
|
+
if (!session) return null;
|
|
7240
|
+
console.log(`${pc5.cyan("i")} No --map-id given; creating a managed Map for this deploy.`);
|
|
7241
|
+
const result = await setUpManagedMap(session, options, {
|
|
7242
|
+
prompts: dependencies.prompts === void 0 ? createTerminalPrompts() : dependencies.prompts
|
|
7243
|
+
});
|
|
7244
|
+
if (!result.ok) {
|
|
7245
|
+
emitFailure(false, result.code, result.error);
|
|
7246
|
+
return null;
|
|
7247
|
+
}
|
|
7248
|
+
printCreatedMap(result.value);
|
|
7249
|
+
console.log(
|
|
7250
|
+
pc5.gray(
|
|
7251
|
+
`If this deploy is interrupted, rerun it with --setup-key ${result.setupKey} to reuse the same Map.`
|
|
7252
|
+
)
|
|
7253
|
+
);
|
|
7254
|
+
return result.value.map.id;
|
|
7255
|
+
}
|
|
7256
|
+
function printCreatedMap(created) {
|
|
7257
|
+
console.log(
|
|
7258
|
+
`${pc5.green("\u2713")} Created ${pc5.bold(created.map.id)} in ${created.team.name} ${pc5.gray(`@${created.team.slug}`)}.`
|
|
7259
|
+
);
|
|
7260
|
+
console.log(
|
|
7261
|
+
`${pc5.gray("Websites:".padEnd(13))} ${describeBrowserAccess(created.allowedOrigins)}`
|
|
7262
|
+
);
|
|
7263
|
+
}
|
|
7264
|
+
async function loadSession(options, dependencies) {
|
|
7265
|
+
let apiUrl;
|
|
7266
|
+
try {
|
|
7267
|
+
apiUrl = normalizeApiOrigin(options.apiUrl ?? dependencies.defaultApiUrl);
|
|
7268
|
+
} catch (error) {
|
|
7269
|
+
emitFailure(options.json, "invalid_api_url", safeMessage(error));
|
|
7270
|
+
return null;
|
|
7271
|
+
}
|
|
7272
|
+
let config;
|
|
7273
|
+
try {
|
|
7274
|
+
config = await dependencies.loadAuthConfig();
|
|
7275
|
+
} catch (error) {
|
|
7276
|
+
emitFailure(options.json, "auth_state_unavailable", safeMessage(error));
|
|
7277
|
+
return null;
|
|
7278
|
+
}
|
|
7279
|
+
const selected = resolveAccountSession(config, apiUrl);
|
|
7280
|
+
if (selected.kind !== "selected") {
|
|
7281
|
+
emitFailure(
|
|
7282
|
+
options.json,
|
|
7283
|
+
`account_session_${selected.kind}`,
|
|
7284
|
+
selected.kind === "expired" ? "The Tileflow account session has expired. Run tileflow login." : "Creating a Map needs a Tileflow account session. Run tileflow login."
|
|
7285
|
+
);
|
|
7286
|
+
return null;
|
|
7287
|
+
}
|
|
7288
|
+
return selected.session;
|
|
7289
|
+
}
|
|
7290
|
+
|
|
6920
7291
|
// src/model-command.ts
|
|
6921
7292
|
import { lstat as lstat5 } from "fs/promises";
|
|
6922
7293
|
import { basename as basename4, dirname as dirname11, relative as relative4, resolve as resolve8 } from "path";
|
|
6923
|
-
import
|
|
7294
|
+
import pc6 from "picocolors";
|
|
6924
7295
|
import { z as z5 } from "zod";
|
|
6925
7296
|
import { tileflowModelContentType } from "@tileflow/core";
|
|
6926
7297
|
import {
|
|
@@ -6998,7 +7369,7 @@ function registerModelCommands(program2, dependencies) {
|
|
|
6998
7369
|
const current = parsed.data.model.current;
|
|
6999
7370
|
const action = parsed.data.changed ? "Published" : "Unchanged";
|
|
7000
7371
|
console.log(
|
|
7001
|
-
`${
|
|
7372
|
+
`${pc6.green("\u2713")} ${action} 3D model ${pc6.bold(name)} version ${current.version} (${formatBytes3(current.byteLength)}, ${formatCount(current.summary.levels[0]?.triangles ?? 0)} triangles).`
|
|
7002
7373
|
);
|
|
7003
7374
|
}
|
|
7004
7375
|
}
|
|
@@ -7047,7 +7418,7 @@ function registerModelCommands(program2, dependencies) {
|
|
|
7047
7418
|
for (const model of listed) {
|
|
7048
7419
|
const current = model.current;
|
|
7049
7420
|
console.log(
|
|
7050
|
-
`${
|
|
7421
|
+
`${pc6.bold(model.name)} ${current ? `version ${current.version}, ${formatBytes3(current.byteLength)}` : "no current version"} ${model.usedByMaps === 1 ? "1 map" : `${model.usedByMaps} maps`}${model.state === "purging" ? pc6.yellow(" purging") : ""}`
|
|
7051
7422
|
);
|
|
7052
7423
|
}
|
|
7053
7424
|
}
|
|
@@ -7064,7 +7435,7 @@ function registerModelCommands(program2, dependencies) {
|
|
|
7064
7435
|
if (options.json) return emitJson({ ...detail, command: "models status", ok: true });
|
|
7065
7436
|
const { model, versions, uses } = detail;
|
|
7066
7437
|
console.log(
|
|
7067
|
-
`${
|
|
7438
|
+
`${pc6.bold(model.name)}${model.state === "purging" ? pc6.yellow(" (purging)") : ""}`
|
|
7068
7439
|
);
|
|
7069
7440
|
for (const version of versions) {
|
|
7070
7441
|
const role = version.state === "current" ? "current" : version.state === "previous" ? "previous" : version.state;
|
|
@@ -7119,7 +7490,7 @@ function registerModelCommands(program2, dependencies) {
|
|
|
7119
7490
|
model: parsed.data.model
|
|
7120
7491
|
});
|
|
7121
7492
|
console.log(
|
|
7122
|
-
`${
|
|
7493
|
+
`${pc6.green("\u2713")} ${pc6.bold(name)} is at version ${previous.version} again; version ${current.version} is its previous one. Maps draw it on their next load.`
|
|
7123
7494
|
);
|
|
7124
7495
|
});
|
|
7125
7496
|
withTeamOptions2(
|
|
@@ -7156,7 +7527,7 @@ function registerModelCommands(program2, dependencies) {
|
|
|
7156
7527
|
purge: parsed.data.purge
|
|
7157
7528
|
});
|
|
7158
7529
|
console.log(
|
|
7159
|
-
`${
|
|
7530
|
+
`${pc6.green("\u2713")} ${pc6.bold(name)} is being purged; its name is free once its files are deleted.`
|
|
7160
7531
|
);
|
|
7161
7532
|
});
|
|
7162
7533
|
}
|
|
@@ -7420,7 +7791,7 @@ function resolveTileflowExternalOpenInvocation(target, platform) {
|
|
|
7420
7791
|
}
|
|
7421
7792
|
|
|
7422
7793
|
// src/project-commands.ts
|
|
7423
|
-
import
|
|
7794
|
+
import pc7 from "picocolors";
|
|
7424
7795
|
function registerProjectCommands(program2, dependencies) {
|
|
7425
7796
|
const projects = program2.command("projects", { hidden: true }).description("Manage internal application destinations");
|
|
7426
7797
|
projects.command("list").description("List application destinations accessible to the authenticated Tileflow account").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--include-archived", "include archived application destinations").option("--json", "print deterministic schema-version-1 JSON").action(async (options) => {
|
|
@@ -7436,12 +7807,12 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
7436
7807
|
};
|
|
7437
7808
|
if (options.json) return emitJson2(document);
|
|
7438
7809
|
if (!result.items.length) {
|
|
7439
|
-
console.log(
|
|
7810
|
+
console.log(pc7.gray("No managed destinations found."));
|
|
7440
7811
|
return;
|
|
7441
7812
|
}
|
|
7442
7813
|
for (const target of result.items) {
|
|
7443
7814
|
console.log(
|
|
7444
|
-
` ${target.project.archivedAt ?
|
|
7815
|
+
` ${target.project.archivedAt ? pc7.yellow("\u25CB") : pc7.green("\u25CF")} ${pc7.bold(target.project.name)} ${pc7.gray(target.reference)}${target.project.archivedAt ? pc7.gray(" (archived)") : ""}`
|
|
7445
7816
|
);
|
|
7446
7817
|
}
|
|
7447
7818
|
});
|
|
@@ -7494,8 +7865,8 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
7494
7865
|
...asRecord2(result.body)
|
|
7495
7866
|
};
|
|
7496
7867
|
if (options.json) return emitJson2(document);
|
|
7497
|
-
console.log(`${
|
|
7498
|
-
console.log(
|
|
7868
|
+
console.log(`${pc7.green("\u2713")} Application ${pc7.bold(options.name.trim())} is ready.`);
|
|
7869
|
+
console.log(pc7.gray(`Its technical selector is --project @${organization.slug}/${slug}.`));
|
|
7499
7870
|
}
|
|
7500
7871
|
);
|
|
7501
7872
|
projects.command("show <target>").description("Show one application destination").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--json", "print deterministic schema-version-1 JSON").action(async (target, options) => {
|
|
@@ -7517,8 +7888,8 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
7517
7888
|
if (!isProjectItem(project)) {
|
|
7518
7889
|
return emitLocalFailure(false, "invalid_response", "Destination response was invalid.");
|
|
7519
7890
|
}
|
|
7520
|
-
console.log(`${
|
|
7521
|
-
console.log(project.archivedAt ?
|
|
7891
|
+
console.log(`${pc7.bold(project.name)} ${pc7.gray(target)}`);
|
|
7892
|
+
console.log(project.archivedAt ? pc7.yellow("Archived") : pc7.green("Active"));
|
|
7522
7893
|
});
|
|
7523
7894
|
for (const archived of [true, false]) {
|
|
7524
7895
|
const verb = archived ? "archive" : "unarchive";
|
|
@@ -7545,7 +7916,7 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
7545
7916
|
...asRecord2(result.body)
|
|
7546
7917
|
};
|
|
7547
7918
|
if (options.json) return emitJson2(document);
|
|
7548
|
-
console.log(`${
|
|
7919
|
+
console.log(`${pc7.green("\u2713")} Application ${archived ? "archived" : "unarchived"}.`);
|
|
7549
7920
|
});
|
|
7550
7921
|
}
|
|
7551
7922
|
}
|
|
@@ -7656,7 +8027,7 @@ function emitLocalFailure(json2, code, message, details = {}) {
|
|
|
7656
8027
|
console.error(message);
|
|
7657
8028
|
emitJson2({ schemaVersion: 1, ok: false, error: { code, ...details } });
|
|
7658
8029
|
} else {
|
|
7659
|
-
console.error(`${
|
|
8030
|
+
console.error(`${pc7.red("\u2715")} ${message}`);
|
|
7660
8031
|
}
|
|
7661
8032
|
process.exitCode = 1;
|
|
7662
8033
|
}
|
|
@@ -7800,7 +8171,7 @@ async function runRendererArtifactCommand(command2, options) {
|
|
|
7800
8171
|
}
|
|
7801
8172
|
|
|
7802
8173
|
// src/tileset-command.ts
|
|
7803
|
-
import
|
|
8174
|
+
import pc8 from "picocolors";
|
|
7804
8175
|
import { z as z7 } from "zod";
|
|
7805
8176
|
import { serializeCanonicalJson as serializeCanonicalJson6 } from "@tileflow/core";
|
|
7806
8177
|
import { inspectTileflowPmtiles } from "@tileflow/dev/tilesets";
|
|
@@ -8459,7 +8830,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
|
|
|
8459
8830
|
`);
|
|
8460
8831
|
return;
|
|
8461
8832
|
}
|
|
8462
|
-
console.log(`${
|
|
8833
|
+
console.log(`${pc8.green("\u2713")} PMTiles archive is valid.`);
|
|
8463
8834
|
console.log(` Type: ${inspection.contract.tileType}`);
|
|
8464
8835
|
console.log(` Zooms: ${inspection.contract.minzoom}-${inspection.contract.maxzoom}`);
|
|
8465
8836
|
console.log(` Source layers: ${inspection.contract.sourceLayers.length}`);
|
|
@@ -8469,7 +8840,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
|
|
|
8469
8840
|
);
|
|
8470
8841
|
}
|
|
8471
8842
|
for (const warning of inspection.warnings) {
|
|
8472
|
-
console.log(`${
|
|
8843
|
+
console.log(`${pc8.yellow("!")} ${warning.message}`);
|
|
8473
8844
|
}
|
|
8474
8845
|
} catch (error) {
|
|
8475
8846
|
const record = asRecord4(error);
|
|
@@ -8482,7 +8853,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
|
|
|
8482
8853
|
`
|
|
8483
8854
|
);
|
|
8484
8855
|
} else {
|
|
8485
|
-
console.error(`${
|
|
8856
|
+
console.error(`${pc8.red("Error:")} ${message}`);
|
|
8486
8857
|
}
|
|
8487
8858
|
process.exitCode = 1;
|
|
8488
8859
|
}
|
|
@@ -8570,7 +8941,7 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
8570
8941
|
};
|
|
8571
8942
|
if (options.json) return emitJson(document);
|
|
8572
8943
|
console.log(
|
|
8573
|
-
`${
|
|
8944
|
+
`${pc8.green("\u2713")} ${publication.changed ? "Published" : "Unchanged"} ${pc8.bold(options.id)} in ${authority.team ? `@${authority.team.slug}` : "the Team bound to the data key"}.`
|
|
8574
8945
|
);
|
|
8575
8946
|
} catch (error) {
|
|
8576
8947
|
if (error instanceof TeamTilesetUploadError) {
|
|
@@ -8590,9 +8961,9 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
8590
8961
|
const items = await fetchAllTeamTilesets(authority);
|
|
8591
8962
|
const document = { command: "tileset list", schemaVersion: 1, tilesets: items };
|
|
8592
8963
|
if (options.json) return emitJson(document);
|
|
8593
|
-
if (items.length === 0) return console.log(
|
|
8964
|
+
if (items.length === 0) return console.log(pc8.gray("No Team tilesets."));
|
|
8594
8965
|
for (const item of items) {
|
|
8595
|
-
console.log(` ${
|
|
8966
|
+
console.log(` ${pc8.bold(item.logicalName)} ${pc8.gray(item.status)}`);
|
|
8596
8967
|
}
|
|
8597
8968
|
} catch {
|
|
8598
8969
|
emitFailure(options.json, "tileset_list_failed", "Tileset list failed.");
|
|
@@ -8620,7 +8991,7 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
8620
8991
|
const document = { command: "tileset status", schemaVersion: 1, tileset: parsed.tileset };
|
|
8621
8992
|
if (options.json) return emitJson(document);
|
|
8622
8993
|
const item = parsed.tileset;
|
|
8623
|
-
console.log(`${
|
|
8994
|
+
console.log(`${pc8.bold(item.name)} ${pc8.gray(item.state)}`);
|
|
8624
8995
|
console.log(` ID: ${item.id}`);
|
|
8625
8996
|
console.log(` Current version: ${String(item.currentVersion ?? "unpublished")}`);
|
|
8626
8997
|
} catch {
|
|
@@ -8671,7 +9042,7 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
8671
9042
|
const parsed = tilesetPurgeResponseSchema.parse(response.body);
|
|
8672
9043
|
const document = { command: "tileset purge", schemaVersion: 1, ...parsed };
|
|
8673
9044
|
if (options.json) return emitJson(document);
|
|
8674
|
-
console.log(`${
|
|
9045
|
+
console.log(`${pc8.green("\u2713")} Purge ${parsed.reclamation}.`);
|
|
8675
9046
|
} catch {
|
|
8676
9047
|
emitFailure(options.json, "tileset_purge_failed", "Tileset purge failed.");
|
|
8677
9048
|
}
|
|
@@ -9611,7 +9982,7 @@ var defaultApiUrl = defaultTileflowApiUrl;
|
|
|
9611
9982
|
var defaultAppUrl = "https://tileflow.dev";
|
|
9612
9983
|
var defaultConfigPath = defaultTileflowConfigPath2;
|
|
9613
9984
|
var defaultManifestPath = defaultTileflowManifestPath;
|
|
9614
|
-
program.name("tileflow").description("Beautiful maps from config.").enablePositionalOptions().showHelpAfterError(
|
|
9985
|
+
program.name("tileflow").description("Beautiful maps from config.").enablePositionalOptions().showHelpAfterError(pc9.gray("\nRun tileflow <command> --help for usage.")).showSuggestionAfterError();
|
|
9615
9986
|
program.addHelpText("after", "\n -V, --version output the version number\n");
|
|
9616
9987
|
registerDescribeCommand(program);
|
|
9617
9988
|
registerLanguageCommand(program);
|
|
@@ -9636,9 +10007,9 @@ program.command("init").description("Create a starter tileflow.config.ts").optio
|
|
|
9636
10007
|
}
|
|
9637
10008
|
logSuccess(`Created ${pathLabel(defaultConfigPath)}.`);
|
|
9638
10009
|
printKeyValue("Path", pathLabel(configPath));
|
|
9639
|
-
printKeyValue("Map",
|
|
10010
|
+
printKeyValue("Map", pc9.bold("madrid"));
|
|
9640
10011
|
printNextSteps([
|
|
9641
|
-
`Install ${
|
|
10012
|
+
`Install ${pc9.cyan("@tileflow/core")} and ${pc9.cyan("@tileflow/maps")} in this project if needed.`,
|
|
9642
10013
|
`Preview locally with ${command("tileflow preview")}.`,
|
|
9643
10014
|
`Validate the config with ${command("tileflow validate")}.`
|
|
9644
10015
|
]);
|
|
@@ -10205,12 +10576,17 @@ program.command("preview").description("Open Map DevTools with inspection and op
|
|
|
10205
10576
|
});
|
|
10206
10577
|
}
|
|
10207
10578
|
);
|
|
10208
|
-
|
|
10579
|
+
addMapSetupOptions(
|
|
10580
|
+
program.command("deploy").description(
|
|
10581
|
+
"Deploy maps to Tileflow and write the frontend manifest; without --map-id, create the Map first"
|
|
10582
|
+
)
|
|
10583
|
+
).option("-c, --config <path>", "config path", defaultConfigPath).option("--manifest <path>", "manifest path written for frontend bundlers", defaultManifestPath).option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Tileflow API key", process.env.TILEFLOW_API_KEY).option("--map-id <id>", "managed Map destination; omit it to create a new Map").option("--with-native", "publish web and native-v1 together for the selected managed Map").option("--map <name>", "configured map to connect when the repository contains multiple maps").option(
|
|
10209
10584
|
"--overwrite-self-hosted-manifest",
|
|
10210
10585
|
"explicitly replace an existing self-hosted manifest at --manifest"
|
|
10211
10586
|
).option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").action(
|
|
10212
10587
|
async (options) => {
|
|
10213
10588
|
const source = resolveDeploySource(process.env);
|
|
10589
|
+
if (!validateMapSetupTarget(options, source)) return;
|
|
10214
10590
|
const resolveApi = () => requireApiOptions(options, {
|
|
10215
10591
|
allowStoredCredential: allowsStoredDeployCredential(source),
|
|
10216
10592
|
assertExplicitKeyMap: true,
|
|
@@ -10230,7 +10606,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10230
10606
|
);
|
|
10231
10607
|
if (result) {
|
|
10232
10608
|
logSuccess(
|
|
10233
|
-
`${result.changed ? "Published" : "Unchanged"} ${
|
|
10609
|
+
`${result.changed ? "Published" : "Unchanged"} ${pc9.bold(result.mapName)} web + native-v1 (v${result.version}).`
|
|
10234
10610
|
);
|
|
10235
10611
|
printKeyValue("Manifest", pathLabel(result.manifestPath));
|
|
10236
10612
|
printKeyValue("Native", link(result.nativeManifestUrl));
|
|
@@ -10325,6 +10701,21 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10325
10701
|
return;
|
|
10326
10702
|
}
|
|
10327
10703
|
const existingManifest = outputManifest;
|
|
10704
|
+
let createdMapId = null;
|
|
10705
|
+
if (!options.mapId) {
|
|
10706
|
+
const deployedMapId2 = existingManifest?.maps[mapNames[0]]?.mapId;
|
|
10707
|
+
if (deployedMapId2) {
|
|
10708
|
+
logError(`${pc9.bold(mapNames[0])} is already deployed to ${deployedMapId2}.`);
|
|
10709
|
+
printNextSteps([
|
|
10710
|
+
`Deploy it again with ${command(`tileflow deploy --map-id ${deployedMapId2}`)}.`
|
|
10711
|
+
]);
|
|
10712
|
+
process.exitCode = 1;
|
|
10713
|
+
return;
|
|
10714
|
+
}
|
|
10715
|
+
createdMapId = await createMapForDeploy(options, { defaultApiUrl, loadAuthConfig });
|
|
10716
|
+
if (!createdMapId) return;
|
|
10717
|
+
options.mapId = createdMapId;
|
|
10718
|
+
}
|
|
10328
10719
|
const api = await resolveApi();
|
|
10329
10720
|
if (!api) return;
|
|
10330
10721
|
const hostedSpriteByPackageHash = /* @__PURE__ */ new Map();
|
|
@@ -10344,7 +10735,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10344
10735
|
);
|
|
10345
10736
|
const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
|
|
10346
10737
|
logSuccess(
|
|
10347
|
-
`${action} icon package ${
|
|
10738
|
+
`${action} icon package ${pc9.bold(binding?.label ?? "Icons")} (${plural(iconPackage.manifest.iconNames.length, "icon")}, ${formatBytes4(totalBytes)}, ${iconPackage.contentHash.slice(0, 12)}).`
|
|
10348
10739
|
);
|
|
10349
10740
|
hostedSpriteByPackageHash.set(iconPackage.contentHash, uploaded.value.spriteUrl);
|
|
10350
10741
|
}
|
|
@@ -10506,7 +10897,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10506
10897
|
}
|
|
10507
10898
|
const themeNames = Object.keys(themeStyles).sort();
|
|
10508
10899
|
logInfo(
|
|
10509
|
-
`Deploying compiled map ${
|
|
10900
|
+
`Deploying compiled map ${pc9.bold(mapName)} (${plural(themeNames.length, "theme")}).`
|
|
10510
10901
|
);
|
|
10511
10902
|
const response = await publishHostedStyle(
|
|
10512
10903
|
api,
|
|
@@ -10588,14 +10979,14 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10588
10979
|
};
|
|
10589
10980
|
const versionLabel = Number.isInteger(body.version) ? ` (v${body.version})` : "";
|
|
10590
10981
|
if (body.changed === false) {
|
|
10591
|
-
logSuccess(`Unchanged ${
|
|
10982
|
+
logSuccess(`Unchanged ${pc9.bold(mapName)}${versionLabel}.`);
|
|
10592
10983
|
} else {
|
|
10593
|
-
logSuccess(`Published ${
|
|
10984
|
+
logSuccess(`Published ${pc9.bold(mapName)}${versionLabel}.`);
|
|
10594
10985
|
}
|
|
10595
10986
|
for (const teamModel of body.teamModels ?? []) {
|
|
10596
10987
|
if (teamModel.sources !== "differ") continue;
|
|
10597
10988
|
logWarning(
|
|
10598
|
-
`3D model ${
|
|
10989
|
+
`3D model ${pc9.bold(teamModel.model)} draws its published version ${teamModel.version}, which was not prepared from this project's files. Run \`tileflow models publish\` to publish them.`
|
|
10599
10990
|
);
|
|
10600
10991
|
}
|
|
10601
10992
|
}
|
|
@@ -10612,6 +11003,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10612
11003
|
const deployedMapId = Object.values(deployedMaps)[0]?.mapId;
|
|
10613
11004
|
if (deployedMapId) {
|
|
10614
11005
|
printNextSteps([
|
|
11006
|
+
...createdMapId ? [`Deploy later changes with ${command(`tileflow deploy --map-id ${createdMapId}`)}.`] : [],
|
|
10615
11007
|
`Check hosted state with ${command(`tileflow status --map-id ${deployedMapId}`)}.`
|
|
10616
11008
|
]);
|
|
10617
11009
|
}
|
|
@@ -10712,6 +11104,7 @@ registerProjectCommands(program, {
|
|
|
10712
11104
|
defaultApiUrl,
|
|
10713
11105
|
loadAuthConfig
|
|
10714
11106
|
});
|
|
11107
|
+
registerMapCommands(program, { defaultApiUrl, loadAuthConfig });
|
|
10715
11108
|
if (isRootCliVersionRequest(process.argv.slice(2))) {
|
|
10716
11109
|
process.stdout.write(`${packageJson.version}
|
|
10717
11110
|
`);
|
|
@@ -11003,17 +11396,17 @@ async function loginWithDeviceFlow(options) {
|
|
|
11003
11396
|
codeChallenge,
|
|
11004
11397
|
deviceName: hostname()
|
|
11005
11398
|
});
|
|
11006
|
-
printKeyValue("Code",
|
|
11399
|
+
printKeyValue("Code", pc9.bold(authorization.userCode));
|
|
11007
11400
|
printKeyValue("URL", link(authorization.verificationUriComplete));
|
|
11008
11401
|
if (options.noBrowser) {
|
|
11009
11402
|
logMuted("Open the URL above to continue.");
|
|
11010
11403
|
} else if (process.stdin.isTTY) {
|
|
11011
|
-
const readline =
|
|
11404
|
+
const readline = createInterface2({
|
|
11012
11405
|
input: process.stdin,
|
|
11013
11406
|
output: process.stdout
|
|
11014
11407
|
});
|
|
11015
11408
|
try {
|
|
11016
|
-
await readline.question(`${
|
|
11409
|
+
await readline.question(`${pc9.cyan("?")} Press Enter to open Tileflow Dashboard...`);
|
|
11017
11410
|
openBrowser(authorization.verificationUriComplete);
|
|
11018
11411
|
} finally {
|
|
11019
11412
|
readline.close();
|
|
@@ -11050,7 +11443,7 @@ function createPkceChallenge(verifier) {
|
|
|
11050
11443
|
return createHash4("sha256").update(verifier).digest("base64url");
|
|
11051
11444
|
}
|
|
11052
11445
|
function randomBase64Url(byteLength) {
|
|
11053
|
-
return
|
|
11446
|
+
return randomBytes3(byteLength).toString("base64url");
|
|
11054
11447
|
}
|
|
11055
11448
|
function openBrowser(url, errorsToStderr = false) {
|
|
11056
11449
|
openTileflowExternal(url, {
|
|
@@ -11102,11 +11495,6 @@ function createCompiledMapAssets(compiled, resolveSprite, modelAssets) {
|
|
|
11102
11495
|
};
|
|
11103
11496
|
}
|
|
11104
11497
|
function selectManagedDeployMaps(mapNames, options) {
|
|
11105
|
-
if (options.map && !options.mapId) {
|
|
11106
|
-
logError("--map requires --map-id.");
|
|
11107
|
-
process.exitCode = 1;
|
|
11108
|
-
return null;
|
|
11109
|
-
}
|
|
11110
11498
|
if (options.map) {
|
|
11111
11499
|
if (!mapNames.includes(options.map)) {
|
|
11112
11500
|
logError(`Unknown Tileflow map: ${options.map}.`);
|
|
@@ -11116,24 +11504,40 @@ function selectManagedDeployMaps(mapNames, options) {
|
|
|
11116
11504
|
}
|
|
11117
11505
|
return [options.map];
|
|
11118
11506
|
}
|
|
11119
|
-
if (!options.mapId) return mapNames;
|
|
11120
11507
|
if (mapNames.length === 1) return [mapNames[0]];
|
|
11121
11508
|
logError(
|
|
11122
11509
|
"This managed target requires an explicit map because the config contains multiple maps."
|
|
11123
11510
|
);
|
|
11124
11511
|
printKeyValue("Maps", mapNames.join(", "));
|
|
11125
11512
|
printNextSteps([
|
|
11126
|
-
`Retry with ${command(`tileflow deploy
|
|
11513
|
+
`Retry with ${command(`tileflow deploy ${options.mapId ? `--map-id ${options.mapId} ` : ""}--map ${mapNames[0]}`)}.`
|
|
11127
11514
|
]);
|
|
11128
11515
|
process.exitCode = 1;
|
|
11129
11516
|
return null;
|
|
11130
11517
|
}
|
|
11131
|
-
function
|
|
11132
|
-
|
|
11133
|
-
logError(
|
|
11518
|
+
function validateMapSetupTarget(options, source) {
|
|
11519
|
+
const fail = (message, steps = []) => {
|
|
11520
|
+
logError(message);
|
|
11521
|
+
printNextSteps(steps);
|
|
11134
11522
|
process.exitCode = 1;
|
|
11135
11523
|
return false;
|
|
11524
|
+
};
|
|
11525
|
+
if (options.mapId) {
|
|
11526
|
+
return hasMapSetupOptions(options) ? fail(
|
|
11527
|
+
"--team, --origin, --any-website and --setup-key apply only when deploy creates a Map."
|
|
11528
|
+
) : true;
|
|
11529
|
+
}
|
|
11530
|
+
const createFirst = `Create the Map with ${command("tileflow maps create")} after ${command("tileflow login")}, then pass --map-id.`;
|
|
11531
|
+
if (options.withNative) return fail("--with-native requires one valid --map-id.", [createFirst]);
|
|
11532
|
+
if (options.apiKey || !allowsStoredDeployCredential(source)) {
|
|
11533
|
+
return fail("Managed deploy with a Tileflow credential or in CI requires --map-id.", [
|
|
11534
|
+
createFirst
|
|
11535
|
+
]);
|
|
11136
11536
|
}
|
|
11537
|
+
const checked = checkMapSetupOptions(options);
|
|
11538
|
+
return checked.ok ? true : fail(checked.error);
|
|
11539
|
+
}
|
|
11540
|
+
function validateDeployTarget(options) {
|
|
11137
11541
|
if (options.mapId && !/^map_[A-Za-z0-9_-]{16}$/u.test(options.mapId)) {
|
|
11138
11542
|
logError("Managed Map ID is invalid.");
|
|
11139
11543
|
process.exitCode = 1;
|
|
@@ -11214,7 +11618,7 @@ async function requireApiOptions(options, behavior = {}) {
|
|
|
11214
11618
|
if (!behavior.silent) {
|
|
11215
11619
|
logError("CI requires an explicit Tileflow credential with access to the target Map.");
|
|
11216
11620
|
printNextSteps([
|
|
11217
|
-
`Set ${
|
|
11621
|
+
`Set ${pc9.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
|
|
11218
11622
|
"The saved personal account session is never used in CI."
|
|
11219
11623
|
]);
|
|
11220
11624
|
}
|
|
@@ -11283,17 +11687,17 @@ function printCliError(error) {
|
|
|
11283
11687
|
}
|
|
11284
11688
|
const message = error instanceof Error ? error.message : "Command failed.";
|
|
11285
11689
|
if (message.includes("Cannot find module '@tileflow/core'")) {
|
|
11286
|
-
logError(`Cannot load ${
|
|
11690
|
+
logError(`Cannot load ${pc9.cyan("@tileflow/core")} from this project.`);
|
|
11287
11691
|
printNextSteps([
|
|
11288
|
-
`Install ${
|
|
11692
|
+
`Install ${pc9.cyan("@tileflow/core")} in this project.`,
|
|
11289
11693
|
`Run the command again from the project root.`
|
|
11290
11694
|
]);
|
|
11291
11695
|
return;
|
|
11292
11696
|
}
|
|
11293
11697
|
if (message.includes("Cannot find module '@tileflow/maps'")) {
|
|
11294
|
-
logError(`Cannot load ${
|
|
11698
|
+
logError(`Cannot load ${pc9.cyan("@tileflow/maps")} from this project.`);
|
|
11295
11699
|
printNextSteps([
|
|
11296
|
-
`Install ${
|
|
11700
|
+
`Install ${pc9.cyan("@tileflow/maps")} in this project.`,
|
|
11297
11701
|
`Run the command again from the project root.`
|
|
11298
11702
|
]);
|
|
11299
11703
|
return;
|
|
@@ -11304,51 +11708,51 @@ function printCliError(error) {
|
|
|
11304
11708
|
}
|
|
11305
11709
|
}
|
|
11306
11710
|
function logSuccess(message) {
|
|
11307
|
-
console.log(`${
|
|
11711
|
+
console.log(`${pc9.green("\u2713")} ${message}`);
|
|
11308
11712
|
}
|
|
11309
11713
|
function logError(message) {
|
|
11310
|
-
console.log(`${
|
|
11714
|
+
console.log(`${pc9.red("\u2715")} ${message}`);
|
|
11311
11715
|
}
|
|
11312
11716
|
function logWarning(message) {
|
|
11313
|
-
console.log(`${
|
|
11717
|
+
console.log(`${pc9.yellow("!")} ${message}`);
|
|
11314
11718
|
}
|
|
11315
11719
|
function logInfo(message) {
|
|
11316
|
-
console.log(`${
|
|
11720
|
+
console.log(`${pc9.cyan("i")} ${message}`);
|
|
11317
11721
|
}
|
|
11318
11722
|
function logMuted(message) {
|
|
11319
|
-
console.log(
|
|
11723
|
+
console.log(pc9.gray(message));
|
|
11320
11724
|
}
|
|
11321
11725
|
function printTitle(title, subtitle) {
|
|
11322
|
-
console.log(
|
|
11726
|
+
console.log(pc9.bold(title));
|
|
11323
11727
|
if (subtitle) {
|
|
11324
|
-
console.log(
|
|
11728
|
+
console.log(pc9.gray(subtitle));
|
|
11325
11729
|
}
|
|
11326
11730
|
}
|
|
11327
11731
|
function printKeyValue(label, value) {
|
|
11328
|
-
console.log(`${
|
|
11732
|
+
console.log(`${pc9.gray(`${label}:`.padEnd(13))} ${value}`);
|
|
11329
11733
|
}
|
|
11330
11734
|
function printChecks(items) {
|
|
11331
11735
|
for (const item of items) {
|
|
11332
|
-
console.log(` ${
|
|
11736
|
+
console.log(` ${pc9.green("\u2713")} ${item}`);
|
|
11333
11737
|
}
|
|
11334
11738
|
}
|
|
11335
11739
|
function printNextSteps(steps) {
|
|
11336
11740
|
if (steps.length === 0) return;
|
|
11337
11741
|
console.log(`
|
|
11338
|
-
${
|
|
11742
|
+
${pc9.bold("Next steps")}`);
|
|
11339
11743
|
for (const step of steps) {
|
|
11340
|
-
console.log(` ${
|
|
11744
|
+
console.log(` ${pc9.cyan("\u2022")} ${step}`);
|
|
11341
11745
|
}
|
|
11342
11746
|
}
|
|
11343
11747
|
function printValidationErrors(messages) {
|
|
11344
11748
|
logError("Tileflow config has errors.");
|
|
11345
11749
|
for (const message of messages) {
|
|
11346
|
-
const path = message.path ?
|
|
11347
|
-
console.log(` ${
|
|
11750
|
+
const path = message.path ? pc9.cyan(message.path) : pc9.cyan("(root)");
|
|
11751
|
+
console.log(` ${pc9.red("\u2715")} ${path} ${pc9.gray("-")} ${message.message}`);
|
|
11348
11752
|
}
|
|
11349
11753
|
}
|
|
11350
11754
|
function printAccountDetails(session) {
|
|
11351
|
-
printKeyValue("Account",
|
|
11755
|
+
printKeyValue("Account", pc9.bold(session.account.name));
|
|
11352
11756
|
printKeyValue("Email", session.account.email);
|
|
11353
11757
|
printKeyValue("API", link(session.apiOrigin));
|
|
11354
11758
|
printKeyValue("Expires", formatDate(session.expiresAt));
|
|
@@ -11377,11 +11781,11 @@ function printDeployedMaps(maps) {
|
|
|
11377
11781
|
const entries = Object.entries(maps);
|
|
11378
11782
|
if (entries.length === 0) return;
|
|
11379
11783
|
console.log(`
|
|
11380
|
-
${
|
|
11784
|
+
${pc9.bold("Maps")}`);
|
|
11381
11785
|
for (const [name, map] of entries) {
|
|
11382
11786
|
for (const [themeName, theme] of Object.entries(map.themes)) {
|
|
11383
11787
|
const label = `${name}/${themeName}`;
|
|
11384
|
-
console.log(` ${
|
|
11788
|
+
console.log(` ${pc9.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
|
|
11385
11789
|
}
|
|
11386
11790
|
}
|
|
11387
11791
|
}
|
|
@@ -11399,13 +11803,13 @@ function plural(count, singular, pluralForm = `${singular}s`) {
|
|
|
11399
11803
|
return `${count} ${count === 1 ? singular : pluralForm}`;
|
|
11400
11804
|
}
|
|
11401
11805
|
function command(value) {
|
|
11402
|
-
return
|
|
11806
|
+
return pc9.cyan(value);
|
|
11403
11807
|
}
|
|
11404
11808
|
function link(value) {
|
|
11405
|
-
return
|
|
11809
|
+
return pc9.cyan(value);
|
|
11406
11810
|
}
|
|
11407
11811
|
function pathLabel(value) {
|
|
11408
|
-
return
|
|
11812
|
+
return pc9.cyan(value);
|
|
11409
11813
|
}
|
|
11410
11814
|
function starterConfig() {
|
|
11411
11815
|
return `import { defineMap, defineTheme, labels, poi } from "@tileflow/core";
|
|
@@ -11451,15 +11855,15 @@ function printHostedCompatibilityIssues(issues) {
|
|
|
11451
11855
|
}
|
|
11452
11856
|
function printMapStatus(status, apiUrl) {
|
|
11453
11857
|
printTitle("Tileflow status");
|
|
11454
|
-
printKeyValue("Map",
|
|
11858
|
+
printKeyValue("Map", pc9.bold(status.mapId));
|
|
11455
11859
|
console.log(`
|
|
11456
|
-
${
|
|
11860
|
+
${pc9.bold("Styles")}`);
|
|
11457
11861
|
if (status.styles.length === 0) {
|
|
11458
11862
|
logMuted(" No styles deployed.");
|
|
11459
11863
|
}
|
|
11460
11864
|
for (const style of status.styles) {
|
|
11461
11865
|
console.log(
|
|
11462
|
-
` ${
|
|
11866
|
+
` ${pc9.green("\u2713")} ${style.environment.padEnd(16)} ${link(`${apiUrl}/maps/${style.mapId}/style.json`).padEnd(36)} ${formatBytes4(style.size).padStart(10)} ${pc9.gray(formatDate(style.uploaded))}`
|
|
11463
11867
|
);
|
|
11464
11868
|
}
|
|
11465
11869
|
}
|
|
@@ -30,8 +30,41 @@ account or hosted command stops with a clear message and asks you to run `tilefl
|
|
|
30
30
|
Project API and CI keys are independent project credentials and are not removed by a personal
|
|
31
31
|
password change.
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
## Create the Map from the terminal
|
|
34
|
+
|
|
35
|
+
After `tileflow login`, the CLI can create the Map itself; nothing has to be copied from the
|
|
36
|
+
dashboard. Run `deploy` without `--map-id`, or create the Map first with `maps create`:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
npx tileflow login
|
|
40
|
+
npx tileflow deploy --origin https://www.example.com
|
|
41
|
+
npx tileflow maps create --any-website --json
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The new Map belongs to a Team you administer. When you administer exactly one Team that can add a
|
|
45
|
+
Map, the CLI uses it; when several can, it asks, or takes `--team @team`; when none can, it stops
|
|
46
|
+
and says why (no administered Team, or every Team has reached its Map limit).
|
|
47
|
+
|
|
48
|
+
Browser access is always an explicit choice: `--origin` with an exact website origin such as
|
|
49
|
+
`https://www.example.com` (repeat it for up to 20 sites), or `--any-website`. Paths, wildcards and
|
|
50
|
+
bare host names are rejected. On a terminal the CLI asks; without one (for example when an agent
|
|
51
|
+
runs it), a missing choice stops before anything is created and names the flags. Browser access
|
|
52
|
+
is stored with the Map in Tileflow and can be changed in the dashboard; it is never written to the
|
|
53
|
+
repository.
|
|
54
|
+
|
|
55
|
+
`deploy` creates the Map only after the local config, assets and styles pass, then publishes to it
|
|
56
|
+
in the same run and prints `tileflow deploy --map-id <mapId>` for later deploys. If the manifest
|
|
57
|
+
already records a Map for that repository map, `deploy` stops and shows that command instead of
|
|
58
|
+
creating another Map. Each creation prints a setup key; rerunning an interrupted command with
|
|
59
|
+
`--setup-key <key>` returns the same Map instead of a second one.
|
|
60
|
+
|
|
61
|
+
Map creation uses only the personal session. With `TILEFLOW_API_KEY`, `--api-key`, `--with-native`,
|
|
62
|
+
or in CI, `deploy` requires `--map-id`.
|
|
63
|
+
|
|
64
|
+
## Deploy to an existing Map
|
|
65
|
+
|
|
66
|
+
A Map created in the dashboard, or by an earlier CLI run, is deployed with its exact Map ID. If the
|
|
67
|
+
config contains several maps, select the authored map explicitly:
|
|
35
68
|
|
|
36
69
|
```sh
|
|
37
70
|
npx tileflow deploy \
|
package/docs/native-artifacts.md
CHANGED
|
@@ -82,8 +82,8 @@ Browser-only selections fail before config execution in native mode. In particul
|
|
|
82
82
|
compiler-inspection sidecars, or the dev handler's status/event control routes. Use ordinary/default
|
|
83
83
|
web preview for those workflows.
|
|
84
84
|
|
|
85
|
-
The `native-lowering-v1` preparation policy records fixed globe-to-Mercator normalization
|
|
86
|
-
finite cap/dash layer partitioning in `native-build.json`; it does not change web artifacts.
|
|
85
|
+
The `native-lowering-v1` preparation policy records fixed globe-to-Mercator normalization, removal
|
|
86
|
+
of a browser sky and finite cap/dash layer partitioning in `native-build.json`; it does not change web artifacts.
|
|
87
87
|
Native preparation rejects adaptive/unknown projections, terrain, browser PMTiles/contour protocols,
|
|
88
88
|
missing text providers and unsupported style values rather than degrading them silently. It preserves
|
|
89
89
|
shared icon inheritance and independent sprite densities, and maps verified licensed TTF/OTF faces
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tileflow",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.16",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "CLI-only tools for Tileflow config, capture, visual testing, and deploy.",
|
|
6
6
|
"type": "module",
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@hono/node-server": "2.1.3",
|
|
39
|
-
"@tileflow/capture": ">=0.1.0-alpha.
|
|
39
|
+
"@tileflow/capture": ">=0.1.0-alpha.33 <0.1.0-beta.0",
|
|
40
40
|
"@tileflow/coordinates": ">=0.1.0-alpha.2 <0.1.0-beta.0",
|
|
41
41
|
"@tileflow/coordinates-runtime": ">=0.1.0-alpha.5 <0.1.0-beta.0",
|
|
42
|
-
"@tileflow/core": ">=0.1.0-alpha.
|
|
43
|
-
"@tileflow/dev": ">=0.1.0-alpha.
|
|
44
|
-
"@tileflow/maps": ">=0.1.0-alpha.
|
|
42
|
+
"@tileflow/core": ">=0.1.0-alpha.39 <0.1.0-beta.0",
|
|
43
|
+
"@tileflow/dev": ">=0.1.0-alpha.36 <0.1.0-beta.0",
|
|
44
|
+
"@tileflow/maps": ">=0.1.0-alpha.13 <0.1.0-beta.0",
|
|
45
45
|
"commander": "^15.0.0",
|
|
46
|
-
"hono": "4.13.
|
|
46
|
+
"hono": "4.13.13",
|
|
47
47
|
"picocolors": "^1.1.1",
|
|
48
48
|
"zod": "^4.4.3"
|
|
49
49
|
},
|