@flisk-sh/flisk-import 0.11.0 → 0.12.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
@@ -13,11 +13,12 @@ flisk import . --yes # write service.json files and project setup
13
13
  - **Reads one file:** the root `docker-compose.yml`, or `compose.yaml`, `compose.yml` or `docker-compose.yaml` when that is absent. Not override files, nested Compose files, Dockerfiles on their own, `package.json`, or Procfiles.
14
14
  - **Writes:** one bring-your-own `service.json` per Compose service, plus `.flisk/project.json` if the repo is not a Flisk project yet. It never edits the Compose file or overwrites a `service.json` without `--force`.
15
15
  - **Keeps the install's data:** each named volume gets `name`, the Docker volume `docker compose up` created (`<project>_<volume>`, its own `name:`, or an `external` volume), so `flisk up` mounts the same data with no copy (needs the Flisk release with flisk#2964).
16
+ - **Keeps:** `profiles` (the service starts only with `flisk up --profile`), `env_file` as `envFile`, `tmpfs` and `deploy.resources.limits` (`container.tmpfs`, `cpus`, `memory`).
16
17
  - **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.
17
18
  - **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}` host side as `publish`), `network_mode: service:<name>` (`sidecarOf`) and `restart`.
18
19
  - **Environment:** values that cannot be secrets go into the committed `service.json` `env`: numbers, `true`/`false`, another imported service's address without a user, password or query (`http://invidious:3000`, `redis:6379`), and pure `${VAR}` references. Every other name, and any name that looks like a credential (`PASSWORD`, `TOKEN`, `KEY`, ...), goes into `secrets.required`, with its literal value copied to the project `.env`, which is added to `.gitignore`. Values in `.env` already are kept.
19
20
  - **Keeps:** a `command` or `entrypoint` on an image as the `CMD`/`ENTRYPOINT` of a small `Dockerfile` next to `service.json` (Flisk sets no container command), a `healthcheck` as `healthCheck.command`, a published database port as `extraPorts`, and `restart: on-failure:N` as `on-failure`.
20
- - **Drops, and says so:** `command` on a service with a `build`, `env_file` and every other untranslated key; anonymous volumes and paths outside the project; `network_mode` other than `service:<name>`; services with `profiles`, `extends`, an out-of-tree build or a `${VAR}` image.
21
+ - **Drops, and says so:** `command` on a service with a `build`, `env_file` and every other untranslated key; anonymous volumes and paths outside the project; `network_mode` other than `service:<name>`; services with invalid profile names, `extends`, an out-of-tree build or a `${VAR}` image.
21
22
  - **Does not start anything.** Run `flisk up` with Flisk core **1.4.26 or later** (`publish` needs it). The importer does not check the core version.
22
23
  - **Exit codes:** `0` when the plan succeeds (`--dry-run`, files written, or all `service.json` files already exist), `1` when no root `docker-compose.yml` or mappable service is found, and `2` when the write prompt is declined or closed without an answer (`Nothing written.`).
23
24
 
@@ -47,6 +47,10 @@ const TRANSLATED_KEYS = new Set([
47
47
  "command",
48
48
  "entrypoint",
49
49
  "models",
50
+ "profiles",
51
+ "env_file",
52
+ "tmpfs",
53
+ "deploy",
50
54
  // Flisk gives every service its own stack network and container name, so these change nothing it needs.
51
55
  "networks",
52
56
  "expose",
@@ -357,6 +361,67 @@ function modelsOf(uses, defined) {
357
361
  }
358
362
  return out.length ? out : undefined;
359
363
  }
364
+ const PROFILE = /^[a-z0-9][a-z0-9-]*$/;
365
+ const TMPFS = /^\/[A-Za-z0-9_./-]*(:[a-z0-9=,_-]+)?$/;
366
+ function profilesOf(value) {
367
+ if (!Array.isArray(value) || value.length === 0)
368
+ return undefined;
369
+ return value.every((p) => typeof p === "string" && PROFILE.test(p))
370
+ ? [...new Set(value)]
371
+ : undefined;
372
+ }
373
+ /** Compose env_file entries (a string, strings, or { path, required }) as paths from the import root; undefined when one leaves it. */
374
+ function envFilesOf(value, composeDir) {
375
+ const entries = typeof value === "string" ? [value] : Array.isArray(value) ? value : [];
376
+ const out = [];
377
+ for (const entry of entries) {
378
+ const path = typeof entry === "string" ? entry : isObj(entry) ? entry.path : undefined;
379
+ if (typeof path !== "string" || isAbsolute(path) || path.includes("$"))
380
+ return undefined;
381
+ const resolved = normalize(join(composeDir, path));
382
+ if (resolved === ".." || resolved.startsWith("../") || !/^[A-Za-z0-9_./-]+$/.test(resolved))
383
+ return undefined;
384
+ out.push(resolved);
385
+ }
386
+ return out.length ? out : undefined;
387
+ }
388
+ /** tmpfs and deploy.resources.limits, and the parts that could not be carried over. */
389
+ function runtimeLimitsOf(raw) {
390
+ const dropped = [];
391
+ const out = {};
392
+ if (raw.tmpfs !== undefined) {
393
+ const list = typeof raw.tmpfs === "string" ? [raw.tmpfs] : Array.isArray(raw.tmpfs) ? raw.tmpfs : [];
394
+ const kept = list.filter((t) => typeof t === "string" && TMPFS.test(t));
395
+ if (kept.length !== list.length || list.length === 0)
396
+ dropped.push("tmpfs");
397
+ if (kept.length)
398
+ out.tmpfs = kept;
399
+ }
400
+ if (raw.deploy !== undefined) {
401
+ const deploy = isObj(raw.deploy) ? raw.deploy : {};
402
+ const resources = isObj(deploy.resources) ? deploy.resources : {};
403
+ const limits = isObj(resources.limits) ? resources.limits : {};
404
+ const cpus = typeof limits.cpus === "number" ? String(limits.cpus) : limits.cpus;
405
+ if (typeof cpus === "string" && /^[0-9]+(\.[0-9]{1,2})?$/.test(cpus) && Number(cpus) > 0)
406
+ out.cpus = Number.isInteger(Number(cpus)) && Number(cpus) >= 1 ? Number(cpus) : cpus;
407
+ else if (limits.cpus !== undefined)
408
+ dropped.push("deploy.resources.limits.cpus");
409
+ if (typeof limits.memory === "string" && /^[0-9]+([bBkKmMgG][bB]?)?$/.test(limits.memory))
410
+ out.memory = limits.memory;
411
+ else if (limits.memory !== undefined)
412
+ dropped.push("deploy.resources.limits.memory");
413
+ const other = [
414
+ ...Object.keys(deploy)
415
+ .filter((k) => k !== "resources")
416
+ .map((k) => `deploy.${k}`),
417
+ ...Object.keys(resources)
418
+ .filter((k) => k !== "limits")
419
+ .map((k) => `deploy.resources.${k}`),
420
+ ];
421
+ dropped.push(...other);
422
+ }
423
+ return { ...out, dropped };
424
+ }
360
425
  function healthPathOf(healthcheck) {
361
426
  if (!isObj(healthcheck) || !Array.isArray(healthcheck.test))
362
427
  return undefined;
@@ -423,10 +488,11 @@ registerDetector({
423
488
  });
424
489
  continue;
425
490
  }
426
- if (raw.profiles !== undefined) {
491
+ const profiles = profilesOf(raw.profiles);
492
+ if (raw.profiles !== undefined && !profiles) {
427
493
  skips.push({
428
494
  ...base,
429
- reason: `service "${name}" has profiles ${JSON.stringify(raw.profiles)}; profiles are not enforced by Flisk`,
495
+ reason: `service "${name}" has profiles ${JSON.stringify(raw.profiles)} that Flisk cannot name (lowercase letters, digits and '-'); not imported`,
430
496
  });
431
497
  continue;
432
498
  }
@@ -502,7 +568,22 @@ registerDetector({
502
568
  reason: `service "${name}" health path "${foundHealthPath}" has characters Flisk does not accept (only a-z, 0-9, / and -); not used`,
503
569
  });
504
570
  }
505
- const { container, dropped } = containerOf(raw);
571
+ const { container: options, dropped } = containerOf(raw);
572
+ const limits = runtimeLimitsOf(raw);
573
+ const { dropped: limitsDropped, ...limitValues } = limits;
574
+ const container = options || Object.keys(limitValues).length
575
+ ? { ...(options ?? {}), ...limitValues }
576
+ : undefined;
577
+ for (const key of limitsDropped) {
578
+ skips.push({ ...base, reason: `service "${name}" ${key} is not carried over; not used` });
579
+ }
580
+ const envFile = envFilesOf(raw.env_file, composeDir);
581
+ if (raw.env_file !== undefined && !envFile) {
582
+ skips.push({
583
+ ...base,
584
+ reason: `service "${name}" env_file ${JSON.stringify(raw.env_file)} is outside the imported directory or uses Compose variables; not used`,
585
+ });
586
+ }
506
587
  for (const key of dropped) {
507
588
  skips.push({
508
589
  ...base,
@@ -581,6 +662,8 @@ registerDetector({
581
662
  ...(containerCommand ? { containerCommand } : {}),
582
663
  ...(containerEntrypoint ? { containerEntrypoint } : {}),
583
664
  ...(models ? { models } : {}),
665
+ ...(profiles ? { profiles } : {}),
666
+ ...(envFile ? { envFile } : {}),
584
667
  ...(healthProbe ? { healthCheck: healthProbe } : {}),
585
668
  ...(selectedPort !== undefined ? { port: selectedPort } : {}),
586
669
  ...(healthCheckPath ? { healthCheckPath } : {}),
package/dist/flisk.js CHANGED
@@ -9,7 +9,7 @@ export const SERVICE_MANIFEST_SCHEMA_URL = "https://flisk.sh/schema.service.json
9
9
  * release after 1.4.10 (flisk#2646) accepts a bring-your-own port outside 4000-5999 and two-letter
10
10
  * names, which this importer now writes. Stated, not checked: it does not read the installed version.
11
11
  */
12
- export const MIN_CORE_VERSION = "1.4.24";
12
+ export const MIN_CORE_VERSION = "1.4.26";
13
13
  /**
14
14
  * The core change that runs a registered Tiltfile (`orchestration.source: "tiltfile"`) from `flisk up`. Stated, not
15
15
  * checked, like MIN_CORE_VERSION.
package/dist/merge.js CHANGED
@@ -74,6 +74,8 @@ function mergeGroup(name, dir, members, resolve) {
74
74
  ...firstOf(ordered, "containerCommand"),
75
75
  ...firstOf(ordered, "containerEntrypoint"),
76
76
  ...firstOf(ordered, "models"),
77
+ ...firstOf(ordered, "profiles"),
78
+ ...firstOf(ordered, "envFile"),
77
79
  ...firstOf(ordered, "healthCheck"),
78
80
  ...(ordered.find((c) => c.sidecarOf)?.sidecarOf
79
81
  ? { sidecarOf: resolve(ordered.find((c) => c.sidecarOf)?.sidecarOf) }
package/dist/write.js CHANGED
@@ -261,6 +261,16 @@ export function planWrites(plan, force) {
261
261
  const rel = relative(serviceDir, absolute).split("\\").join("/") || ".";
262
262
  return [{ ...volume, source: rel.startsWith(".") ? rel : `./${rel}` }];
263
263
  });
264
+ // env_file paths are relative to the service directory in service.json, and must stay inside the project.
265
+ const envFile = (service.envFile ?? []).flatMap((file) => {
266
+ const absolute = resolve(plan.root, file);
267
+ const fromProject = relative(projectRoot, absolute).split("\\").join("/");
268
+ if (fromProject === ".." || fromProject.startsWith("../")) {
269
+ notes.push(`env_file ${file} not written: it is outside the project`);
270
+ return [];
271
+ }
272
+ return [relative(serviceDir, absolute).split("\\").join("/")];
273
+ });
264
274
  const manifest = {
265
275
  $schema: SERVICE_MANIFEST_SCHEMA_URL,
266
276
  schemaVersion: SERVICE_MANIFEST_SCHEMA_VERSION,
@@ -280,6 +290,8 @@ export function planWrites(plan, force) {
280
290
  ...(service.restart ? { restart: service.restart } : {}),
281
291
  ...(service.container ? { container: service.container } : {}),
282
292
  ...(service.models ? { models: service.models } : {}),
293
+ ...(service.profiles ? { profiles: service.profiles } : {}),
294
+ ...(envFile.length ? { envFile } : {}),
283
295
  ...(extraPorts.length ? { extraPorts } : {}),
284
296
  ...(sidecarOf ? { sidecarOf } : {}),
285
297
  ...(volumes.length
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flisk-sh/flisk-import",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
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",