flisk-import 0.17.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,7 +17,7 @@ flisk import . --yes # write service.json files and project setup
17
17
  - **Keeps (Flisk 1.4.28):** each `depends_on` condition as a `{ name, condition }` entry (the list form as `service_started`), `build.args` as `buildArgs` (and, for a generated Dockerfile on an image with variables, each variable as a build argument, so `ARG POSTGRES_VERSION` gets the project's value), `logging` (options as strings), bind paths from the environment (`${OM_AGENTS_DIR:-./agents}`, the default rewritten relative to `service.json`), SELinux labels (`:z`, `:Z`, `bind.selinux`) as `selinux`, and images with `${NAME:?message}`. A bind path from a variable without a default (`${STORAGE_DIR}/redis`) and `extra_hosts` with a variable are still not carried over.
18
18
  - **Keeps:** `stop_grace_period` (`container.stopGracePeriod`, such as `30s`), the `aliases` of every network (`container.networkAliases`), `extra_hosts` in list or map form (`container.extraHosts`, such as `host.docker.internal:host-gateway`) and `ulimits` (`container.ulimits`). A service renamed to kebab-case (`lychee_api` becomes `lychee-api`) keeps its Compose name as a network alias, so `http://lychee_api:8000` in another service still resolves. `container_name` is dropped with a note: Flisk names containers itself. These fields, an image with variables and `on-failure:N` need the Flisk core release that reads them; an older core does not apply them.
19
19
  - **Keeps:** the service names (`db` stays `db`, so it stays the hostname), the build context and Dockerfile (or the image), the container port as written (3000, 80, 5432), `depends_on`, and an HTTP health path. Databases and brokers are private, with no proxy route.
20
- - **Runtime keys:** named volumes and bind mounts inside the project (`volumes`), `cap_add`, `cap_drop`, `init`, `read_only`, `user`, `security_opt` and `shm_size` (`container`), extra published ports (`extraPorts`, on the same host address as in Compose: `"80:80"` stays on every interface, `"127.0.0.1:5432:5432"` on loopback, and another address or a `${VAR:-default}` or `${VAR-default}` host side as `publish`), `network_mode: service:<name>` (`sidecarOf`) and `restart`.
20
+ - **Runtime keys:** named volumes and bind mounts inside the project (`volumes`), `cap_add`, `cap_drop`, `init`, `read_only`, `user`, `security_opt` and `shm_size` (`container`), extra published ports (`extraPorts`, on the same host address as in Compose: `"80:80"` stays on every interface (written as `publish: "80"`, so IPv6 is kept; `"0.0.0.0:80:80"` is `bind`), `"127.0.0.1:5432:5432"` on loopback, and another address or a `${VAR:-default}` or `${VAR-default}` host side as `publish`), `network_mode: service:<name>` (`sidecarOf`) and `restart`.
21
21
  - **Environment:** the committed `service.json` `env` gets every pure Compose expression as written, whatever its name (`${DB_PASSWORD:-password}`, `${PORT-8080}`, `${A:-${B:-file}}`), so a fresh clone keeps the defaults, and every literal (`sqlite`, `worker`, `lychee_db`, `http://invidious:3000`) unless its name looks like a credential (`PASSWORD`, `TOKEN`, `KEY`, ...) or the value holds one (a URL with a user or password, a token query parameter, a long random string, a PEM block). Those go into `secrets.required`, with its literal value copied to the project `.env`, which is added to `.gitignore`. Values in `.env` already are kept.
22
22
  - **Round trip:** a routed port that Compose also publishes (`"${APP_PORT:-8000}:8000"`) stays in `extraPorts` too, and a `healthcheck` stays as `healthCheck.command` (with `interval`, `timeout`, `retries` and `start_period` as `startPeriod`) even when an HTTP path was read, so `flisk-export compose` writes a file that `docker compose` starts the same way (LycheeOrg/Lychee round-trips with no difference but names and aliases). A service with no `restart` keeps none, so Flisk runs it with its default `unless-stopped` (unlike Compose, Flisk then restarts it after a crash or a reboot; the plan says so). `restart: "no"` is written only when Compose sets it or another service waits for this one with `condition: service_completed_successfully`, because Flisk runs a `restart: "no"` service as a one-shot job and waits for it to exit, `healthcheck: { disable: true }` or `test: ["NONE"]` becomes `healthCheck: false`, a value that mixes `${VAR}` with literal text (`${SERVER_NAME:-localhost}, php:80`) stays in `env` unless it could hold a credential, `*_FILE` paths (`POSTGRES_PASSWORD_FILE=/run/secrets/pg`) stay in `env`, and the Compose project name (`name:` or the folder's, such as `pl_mail`) is recorded as `composeProjectNames.<stack>` in `.flisk/project.json`, so `flisk-export compose` keeps the container names. A generated Dockerfile for an image with variables declares them as `ARG NAME=default` before `FROM`.
23
23
  - **A tracked `.env`:** a repo that commits its `.env` (Symfony) keeps it as it is, and it is not added to `.gitignore`; values that must stay local go to `.env.local` when git already ignores it, else to `.flisk/.env.import` (added to `.gitignore`), and each service that needs them reads that file as `envFile`.
@@ -265,10 +265,19 @@ function hostSideOf(port, hostIp, hostPort) {
265
265
  const publish = hostIp ? `${hostIp}:${hostPort}` : hostPort;
266
266
  return PUBLISH.test(publish) ? { publish } : undefined;
267
267
  }
268
- if (hostIp === undefined || hostIp === "" || hostIp === "0.0.0.0") {
268
+ if (hostIp === undefined || hostIp === "") {
269
269
  // A bare container port ("3000") gets a random host port from Compose; Flisk keeps it on 127.0.0.1.
270
- return hostPort === undefined ? {} : { ...keepHostPort, bind: "0.0.0.0" };
270
+ if (hostPort === undefined)
271
+ return {};
272
+ // "3000:3000" names no host address: Compose listens on every interface, IPv6 too. `publish` writes the same bare mapping
273
+ // ("3000:3000/tcp"); `bind: "0.0.0.0"` would add the IPv4 address and drop IPv6 (flisk#2996).
274
+ return hostNumber !== undefined && hostNumber >= 1 && hostNumber <= 65535
275
+ ? { publish: String(hostNumber) }
276
+ : undefined;
271
277
  }
278
+ // The file names 0.0.0.0 on purpose, so that is what is kept.
279
+ if (hostIp === "0.0.0.0")
280
+ return { ...keepHostPort, bind: "0.0.0.0" };
272
281
  if (hostIp === "127.0.0.1")
273
282
  return keepHostPort;
274
283
  const publish = `${hostIp}:${hostNumber ?? port}`;
@@ -864,22 +873,21 @@ function runtimeLimitsOf(raw) {
864
873
  }
865
874
  return { ...out, dropped };
866
875
  }
876
+ /** A port in a health URL: digits, or a shell/Compose variable such as `$${PORT:-3000}` that a shell healthcheck uses. */
877
+ const URL_PORT = String.raw `(?:\d+|\$+\{[^}\s]*\}|\$+\w+)`;
878
+ const HEALTH_URL = new RegExp(String.raw `(?:https?:\/\/[^\s"'/]+|(?:^|(?<=[\s"']))(?:[a-z0-9.-]+:${URL_PORT}|(?:localhost|127\.0\.0\.1)(?::${URL_PORT})?))(\/[^\s"']*)`, "i");
879
+ /** The HTTP path a Compose healthcheck command probes (`wget -qO- http://localhost:$${PORT:-3000}/api/health`), if it names one. */
867
880
  function healthPathOf(healthcheck) {
868
881
  if (!isObj(healthcheck) || !Array.isArray(healthcheck.test))
869
882
  return undefined;
870
883
  const test = healthcheck.test
871
884
  .filter((part) => typeof part === "string")
872
885
  .join(" ");
873
- const match = test.match(/(?:https?:\/\/[^\s"'/]+|(?:^|(?<=[\s"']))(?:[a-z0-9.-]+:\d+|localhost|127\.0\.0\.1))(\/[^\s"']*)/i);
874
- if (!match)
875
- return undefined;
876
- try {
877
- const raw = match[0];
878
- return new URL(/^https?:\/\//i.test(raw) ? raw : `http://${raw}`).pathname || "/";
879
- }
880
- catch {
886
+ const path = test.match(HEALTH_URL)?.[1];
887
+ if (path === undefined)
881
888
  return undefined;
882
- }
889
+ // The query and fragment are not part of the route Traefik checks. The port may be a variable, so no URL parsing here.
890
+ return path.split(/[?#]/)[0] || "/";
883
891
  }
884
892
  registerDetector({
885
893
  id: "compose",
package/dist/write.js CHANGED
@@ -333,11 +333,15 @@ export function planWrites(plan, force) {
333
333
  const portText = (p) => `${p.publish ?? p.hostPort ?? p.port}->${p.port}/${p.protocol ?? "tcp"}`;
334
334
  const loopback = extraPorts.filter((p) => !p.bind && !p.publish);
335
335
  const everywhere = extraPorts.filter((p) => p.bind === "0.0.0.0");
336
- const fromCompose = extraPorts.filter((p) => p.publish);
336
+ // A bare mapping ("3000:3000") names no host address: Compose listens on every interface, IPv4 and IPv6 (flisk#2996).
337
+ const bare = extraPorts.filter((p) => p.publish && /^\d+$/.test(p.publish));
338
+ const fromCompose = extraPorts.filter((p) => p.publish && !/^\d+$/.test(p.publish));
337
339
  if (loopback.length)
338
340
  notes.push(`published on 127.0.0.1 only: ${loopback.map(portText).join(", ")}`);
339
341
  if (everywhere.length)
340
342
  notes.push(`published on every interface, as in Compose: ${everywhere.map(portText).join(", ")} (flisk doctor lists these)`);
343
+ if (bare.length)
344
+ notes.push(`published as Compose's bare mapping, on every interface (IPv4 and IPv6): ${bare.map(portText).join(", ")} (flisk doctor lists these)`);
341
345
  if (fromCompose.length)
342
346
  notes.push(`published as Compose wrote the host side: ${fromCompose.map(portText).join(", ")}`);
343
347
  // Bind mounts are relative to the service directory in service.json, and must stay inside the project.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flisk-import",
3
- "version": "0.17.0",
3
+ "version": "0.17.1",
4
4
  "description": "Import a repository's root docker-compose.yml into Flisk service.json files, or register its Tiltfile.",
5
5
  "type": "module",
6
6
  "license": "MIT",