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 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
- Replace the example Map ID with the destination created in Tileflow. It is a public identifier,
230
- not a credential or the portable `id` in your config. A CI deploy key must authorize that destination.
231
- `logout` removes the saved personal session.
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 randomBytes2 } from "crypto";
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 pc7 from "picocolors";
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 pc4 from "picocolors";
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
- `${pc4.green("\u2713")} ${action} 3D model ${pc4.bold(name)} version ${current.version} (${formatBytes3(current.byteLength)}, ${formatCount(current.summary.levels[0]?.triangles ?? 0)} triangles).`
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
- `${pc4.bold(model.name)} ${current ? `version ${current.version}, ${formatBytes3(current.byteLength)}` : "no current version"} ${model.usedByMaps === 1 ? "1 map" : `${model.usedByMaps} maps`}${model.state === "purging" ? pc4.yellow(" purging") : ""}`
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
- `${pc4.bold(model.name)}${model.state === "purging" ? pc4.yellow(" (purging)") : ""}`
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
- `${pc4.green("\u2713")} ${pc4.bold(name)} is at version ${previous.version} again; version ${current.version} is its previous one. Maps draw it on their next load.`
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
- `${pc4.green("\u2713")} ${pc4.bold(name)} is being purged; its name is free once its files are deleted.`
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 pc5 from "picocolors";
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(pc5.gray("No managed destinations found."));
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 ? pc5.yellow("\u25CB") : pc5.green("\u25CF")} ${pc5.bold(target.project.name)} ${pc5.gray(target.reference)}${target.project.archivedAt ? pc5.gray(" (archived)") : ""}`
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(`${pc5.green("\u2713")} Application ${pc5.bold(options.name.trim())} is ready.`);
7498
- console.log(pc5.gray(`Its technical selector is --project @${organization.slug}/${slug}.`));
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(`${pc5.bold(project.name)} ${pc5.gray(target)}`);
7521
- console.log(project.archivedAt ? pc5.yellow("Archived") : pc5.green("Active"));
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(`${pc5.green("\u2713")} Application ${archived ? "archived" : "unarchived"}.`);
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(`${pc5.red("\u2715")} ${message}`);
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 pc6 from "picocolors";
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(`${pc6.green("\u2713")} PMTiles archive is valid.`);
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(`${pc6.yellow("!")} ${warning.message}`);
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(`${pc6.red("Error:")} ${message}`);
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
- `${pc6.green("\u2713")} ${publication.changed ? "Published" : "Unchanged"} ${pc6.bold(options.id)} in ${authority.team ? `@${authority.team.slug}` : "the Team bound to the data key"}.`
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(pc6.gray("No Team tilesets."));
8964
+ if (items.length === 0) return console.log(pc8.gray("No Team tilesets."));
8594
8965
  for (const item of items) {
8595
- console.log(` ${pc6.bold(item.logicalName)} ${pc6.gray(item.status)}`);
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(`${pc6.bold(item.name)} ${pc6.gray(item.state)}`);
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(`${pc6.green("\u2713")} Purge ${parsed.reclamation}.`);
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(pc7.gray("\nRun tileflow <command> --help for usage.")).showSuggestionAfterError();
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", pc7.bold("madrid"));
10010
+ printKeyValue("Map", pc9.bold("madrid"));
9640
10011
  printNextSteps([
9641
- `Install ${pc7.cyan("@tileflow/core")} and ${pc7.cyan("@tileflow/maps")} in this project if needed.`,
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
- program.command("deploy").description("Deploy maps to Tileflow and write the frontend manifest").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").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(
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"} ${pc7.bold(result.mapName)} web + native-v1 (v${result.version}).`
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 ${pc7.bold(binding?.label ?? "Icons")} (${plural(iconPackage.manifest.iconNames.length, "icon")}, ${formatBytes4(totalBytes)}, ${iconPackage.contentHash.slice(0, 12)}).`
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 ${pc7.bold(mapName)} (${plural(themeNames.length, "theme")}).`
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 ${pc7.bold(mapName)}${versionLabel}.`);
10982
+ logSuccess(`Unchanged ${pc9.bold(mapName)}${versionLabel}.`);
10592
10983
  } else {
10593
- logSuccess(`Published ${pc7.bold(mapName)}${versionLabel}.`);
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 ${pc7.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.`
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", pc7.bold(authorization.userCode));
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 = createInterface({
11404
+ const readline = createInterface2({
11012
11405
  input: process.stdin,
11013
11406
  output: process.stdout
11014
11407
  });
11015
11408
  try {
11016
- await readline.question(`${pc7.cyan("?")} Press Enter to open Tileflow Dashboard...`);
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 randomBytes2(byteLength).toString("base64url");
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 --map-id ${options.mapId} --map ${mapNames[0]}`)}.`
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 validateDeployTarget(options) {
11132
- if (!options.mapId) {
11133
- logError("Managed deploy requires --map-id.");
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 ${pc7.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
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 ${pc7.cyan("@tileflow/core")} from this project.`);
11690
+ logError(`Cannot load ${pc9.cyan("@tileflow/core")} from this project.`);
11287
11691
  printNextSteps([
11288
- `Install ${pc7.cyan("@tileflow/core")} in this project.`,
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 ${pc7.cyan("@tileflow/maps")} from this project.`);
11698
+ logError(`Cannot load ${pc9.cyan("@tileflow/maps")} from this project.`);
11295
11699
  printNextSteps([
11296
- `Install ${pc7.cyan("@tileflow/maps")} in this project.`,
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(`${pc7.green("\u2713")} ${message}`);
11711
+ console.log(`${pc9.green("\u2713")} ${message}`);
11308
11712
  }
11309
11713
  function logError(message) {
11310
- console.log(`${pc7.red("\u2715")} ${message}`);
11714
+ console.log(`${pc9.red("\u2715")} ${message}`);
11311
11715
  }
11312
11716
  function logWarning(message) {
11313
- console.log(`${pc7.yellow("!")} ${message}`);
11717
+ console.log(`${pc9.yellow("!")} ${message}`);
11314
11718
  }
11315
11719
  function logInfo(message) {
11316
- console.log(`${pc7.cyan("i")} ${message}`);
11720
+ console.log(`${pc9.cyan("i")} ${message}`);
11317
11721
  }
11318
11722
  function logMuted(message) {
11319
- console.log(pc7.gray(message));
11723
+ console.log(pc9.gray(message));
11320
11724
  }
11321
11725
  function printTitle(title, subtitle) {
11322
- console.log(pc7.bold(title));
11726
+ console.log(pc9.bold(title));
11323
11727
  if (subtitle) {
11324
- console.log(pc7.gray(subtitle));
11728
+ console.log(pc9.gray(subtitle));
11325
11729
  }
11326
11730
  }
11327
11731
  function printKeyValue(label, value) {
11328
- console.log(`${pc7.gray(`${label}:`.padEnd(13))} ${value}`);
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(` ${pc7.green("\u2713")} ${item}`);
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
- ${pc7.bold("Next steps")}`);
11742
+ ${pc9.bold("Next steps")}`);
11339
11743
  for (const step of steps) {
11340
- console.log(` ${pc7.cyan("\u2022")} ${step}`);
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 ? pc7.cyan(message.path) : pc7.cyan("(root)");
11347
- console.log(` ${pc7.red("\u2715")} ${path} ${pc7.gray("-")} ${message.message}`);
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", pc7.bold(session.account.name));
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
- ${pc7.bold("Maps")}`);
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(` ${pc7.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
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 pc7.cyan(value);
11806
+ return pc9.cyan(value);
11403
11807
  }
11404
11808
  function link(value) {
11405
- return pc7.cyan(value);
11809
+ return pc9.cyan(value);
11406
11810
  }
11407
11811
  function pathLabel(value) {
11408
- return pc7.cyan(value);
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", pc7.bold(status.mapId));
11858
+ printKeyValue("Map", pc9.bold(status.mapId));
11455
11859
  console.log(`
11456
- ${pc7.bold("Styles")}`);
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
- ` ${pc7.green("\u2713")} ${style.environment.padEnd(16)} ${link(`${apiUrl}/maps/${style.mapId}/style.json`).padEnd(36)} ${formatBytes4(style.size).padStart(10)} ${pc7.gray(formatDate(style.uploaded))}`
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
- The dashboard creates the Map and browser-access policy before repository setup. Keep that exact
34
- Map ID in the deploy action. If the config contains several maps, select the authored map explicitly:
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 \
@@ -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 and
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.15",
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.32 <0.1.0-beta.0",
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.37 <0.1.0-beta.0",
43
- "@tileflow/dev": ">=0.1.0-alpha.34 <0.1.0-beta.0",
44
- "@tileflow/maps": ">=0.1.0-alpha.11 <0.1.0-beta.0",
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.7",
46
+ "hono": "4.13.13",
47
47
  "picocolors": "^1.1.1",
48
48
  "zod": "^4.4.3"
49
49
  },