@flisk-sh/flisk-export 0.6.0 → 0.7.0

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
@@ -47,9 +47,9 @@ flisk-export compose --check # exit 1 when that file is out of date (for CI)
47
47
  Writes a **standalone** `docker-compose.yml` from the bring-your-own `service.json` files (`image` or `dockerfile`, `env`,
48
48
  `secrets.required`, `envFile`, `profiles`, `models`, `volumes`, `extraPorts`, `dependsOn`, `healthCheck`, `sidecarOf`, `container`). It runs without Flisk (`docker compose --env-file .env -f .autogenerated/docker-compose.yml up -d` from the project root, since Compose looks for `.env` next to the file it reads): no external
49
49
  networks, no `FLISK_*` variables, no generated secrets. `${VAR}` references in `env`, and every name in `secrets.required`, pass through as `${NAME}` for Compose to read from `.env`.
50
- A volume's `name` (the Docker volume an install already has) is kept, and the project is named after its stack, so `docker compose up` finds the same data as before.
50
+ A volume's `name` (the Docker volume an install already has) is kept, and the project is named after its stack (or, when `.flisk/project.json` has `composeProjectNames.<stack>` from flisk-import, the Compose project name it had before, such as `pl_mail`), so `docker compose up` finds the same data as before.
51
51
  An `image` with Compose variables (`postgres:${POSTGRES_VERSION:-18}-alpine`) is written as it is, for Compose to resolve; `restart: "on-failure:N"` keeps N. From `container`: `stopGracePeriod` becomes `stop_grace_period`, `networkAliases` become `networks: { default: { aliases } }` (not on a `sidecarOf` service, which has no network of its own), `extraHosts` become `extra_hosts` and `ulimits` stay `ulimits`. Each value is checked so it cannot add YAML keys; one that does not fit is an error.
52
- `healthCheck.command` becomes a Compose `healthcheck`; a service that lists a checked service in `dependsOn` waits for `service_healthy`, and one that lists a `restart: "no"` job waits for `service_completed_successfully`.
52
+ `healthCheck.command` becomes a Compose `healthcheck` (with `startPeriod` as `start_period`); a service that lists a checked service in `dependsOn` waits for `service_healthy` (only when that healthcheck is written: a service with only `healthCheckPath`, or `enabled: false`, is waited for with `service_started`), and one that lists a `restart: "no"` job waits for `service_completed_successfully`.
53
53
  Other resource types and `command` (native) services are not exported. The file is generated: change `service.json` and run it
54
54
  again with `--force`. It never starts a container or calls an API.
55
55
 
package/dist/compose.js CHANGED
@@ -5,7 +5,8 @@
5
5
  * generated secrets. `${VAR}` references in `env` pass through for Compose to resolve from the project `.env`. These
6
6
  * functions only return text; nothing here starts a container, deploys or calls an API.
7
7
  */
8
- import { relative, sep } from "node:path";
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { join, relative, sep } from "node:path";
9
10
  import { ExportError } from "./errors.js";
10
11
  /** extraPorts `publish`: digits, dots, colons and `${NAME:-default}` / `${NAME-default}`, as flisk core accepts it (flisk#2947). */
11
12
  const PUBLISH = /^(?:[0-9.:]|\$\{[A-Za-z_][A-Za-z0-9_]*:?-[0-9.:]+\})+$/;
@@ -305,7 +306,10 @@ function dependencyCondition(dep) {
305
306
  return "service_completed_successfully";
306
307
  return "service_started";
307
308
  }
309
+ /** Whether the output has a healthcheck for this value: a command, and not turned off. Only then can a dependent wait for service_healthy. */
308
310
  function hasProbeCommand(value) {
311
+ if (value === false || record(value).enabled === false)
312
+ return false;
309
313
  return strings(record(value).command).length > 0;
310
314
  }
311
315
  /** A Compose `healthcheck` from `healthCheck.command` (exit 0 is ready); `healthCheck: false` turns it off. */
@@ -318,14 +322,18 @@ function healthcheckLines(name, value) {
318
322
  if (command.length === 0)
319
323
  return [];
320
324
  const lines = [" healthcheck:", ` test: [${['"CMD"', ...command.map(q)].join(", ")}]`];
321
- for (const key of ["interval", "timeout"]) {
325
+ for (const [key, composeKey] of [
326
+ ["interval", "interval"],
327
+ ["timeout", "timeout"],
328
+ ["startPeriod", "start_period"],
329
+ ]) {
322
330
  if (h[key] === undefined)
323
331
  continue;
324
332
  const seconds = Number(h[key]);
325
333
  if (!Number.isFinite(seconds) || seconds <= 0) {
326
334
  throw new ExportError(`${name}: healthCheck.${key} must be a number of seconds above 0`);
327
335
  }
328
- lines.push(` ${key}: ${q(`${seconds}s`)}`);
336
+ lines.push(` ${composeKey}: ${q(`${seconds}s`)}`);
329
337
  }
330
338
  if (h.retries !== undefined) {
331
339
  const retries = Number(h.retries);
@@ -352,8 +360,26 @@ function joinPosix(base, rel) {
352
360
  }
353
361
  return (base.startsWith("/") ? "/" : "") + out.join("/");
354
362
  }
363
+ /** A Compose project name as `docker compose` accepts it. */
364
+ const PROJECT_NAME = /^[a-z0-9][a-z0-9_-]*$/;
365
+ /**
366
+ * `.flisk/project.json` `composeProjectNames`: the project name each stack had as a Compose project before the import
367
+ * (flisk-import records it), so the export keeps its container names (pl_mail-php-1). Empty when absent or unreadable.
368
+ */
369
+ export function composeProjectNames(projectRoot) {
370
+ const path = join(projectRoot, ".flisk", "project.json");
371
+ if (!existsSync(path))
372
+ return {};
373
+ try {
374
+ const names = record(record(JSON.parse(readFileSync(path, "utf8"))).composeProjectNames);
375
+ return Object.fromEntries(Object.entries(names).filter((entry) => typeof entry[1] === "string" && PROJECT_NAME.test(entry[1])));
376
+ }
377
+ catch {
378
+ return {};
379
+ }
380
+ }
355
381
  /** The text of a docker-compose.yml for `resources`, written for a file in `outDir`. */
356
- export function buildCompose(outDir, resources) {
382
+ export function buildCompose(outDir, resources, options = {}) {
357
383
  const services = resources.filter(isComposeExportable);
358
384
  if (services.length === 0) {
359
385
  throw new ExportError("No bring-your-own resource was found to export", [
@@ -364,6 +390,10 @@ export function buildCompose(outDir, resources) {
364
390
  const stacks = new Set(services
365
391
  .map((s) => s.config.stack)
366
392
  .filter((v) => typeof v === "string" && /^[a-z0-9][a-z0-9_-]*$/.test(v)));
393
+ const projectName = (stack) => {
394
+ const recorded = options.composeProjectNames?.[stack];
395
+ return recorded && PROJECT_NAME.test(recorded) ? recorded : stack;
396
+ };
367
397
  const body = [];
368
398
  const volumes = new Map();
369
399
  const models = new Map();
@@ -392,7 +422,8 @@ export function buildCompose(outDir, resources) {
392
422
  "",
393
423
  // Without a name Compose takes the folder's (autogenerated), so containers and unnamed volumes would not match the
394
424
  // project the app had before. One stack gives the project its name.
395
- ...(stacks.size === 1 ? [`name: ${q([...stacks][0])}`, ""] : []),
425
+ // The Compose project name the stack had before the import, when flisk-import recorded it, else the stack.
426
+ ...(stacks.size === 1 ? [`name: ${q(projectName([...stacks][0]))}`, ""] : []),
396
427
  "services:",
397
428
  ];
398
429
  const tail = [
package/dist/main.js CHANGED
@@ -5,7 +5,7 @@
5
5
  import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
6
6
  import { basename, dirname, join, relative, resolve } from "node:path";
7
7
  import { Command, CommanderError } from "commander";
8
- import { buildCompose } from "./compose.js";
8
+ import { buildCompose, composeProjectNames } from "./compose.js";
9
9
  import { discoverResources, findProjectRoot, findRepoRoot, findResource, } from "./discover.js";
10
10
  import { ExportError } from "./errors.js";
11
11
  import { assertExportable, buildRenderBlueprint, buildWranglerToml, EXPORTABLE_TYPES, isExportable, skippedEnvNames, } from "./generate.js";
@@ -94,7 +94,9 @@ function program(io) {
94
94
  const outDir = options.out
95
95
  ? realDir(resolve(io.cwd, options.out))
96
96
  : join(projectRoot, ".autogenerated");
97
- const content = buildCompose(outDir, discover(projectRoot));
97
+ const content = buildCompose(outDir, discover(projectRoot), {
98
+ composeProjectNames: composeProjectNames(projectRoot),
99
+ });
98
100
  const file = join(outDir, "docker-compose.yml");
99
101
  if (options.check) {
100
102
  const shown = relative(realDir(io.cwd), file) || file;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flisk-sh/flisk-export",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Write a Render Blueprint (render.yaml), a Cloudflare wrangler.toml or a standalone docker-compose.yml from Flisk service.json files. Never deploys.",
5
5
  "type": "module",
6
6
  "license": "MIT",