@flisk-sh/flisk-import 0.8.0 → 0.10.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
@@ -12,6 +12,7 @@ flisk import . --yes # write service.json files and project setup
12
12
 
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
+ - **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).
15
16
  - **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.
16
17
  - **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 127.0.0.1 only), `network_mode: service:<name>` (`sidecarOf`) and `restart`.
17
18
  - **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.
@@ -1,5 +1,5 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { dirname, isAbsolute, join, normalize } from "node:path/posix";
2
+ import { basename, dirname, isAbsolute, join, normalize } from "node:path/posix";
3
3
  import { isAlias, isMap, parseDocument, visit } from "yaml";
4
4
  import { HEALTH_CHECK_PATH_PATTERN } from "../flisk.js";
5
5
  import { registerDetector } from "../registry.js";
@@ -46,6 +46,7 @@ const TRANSLATED_KEYS = new Set([
46
46
  "restart",
47
47
  "command",
48
48
  "entrypoint",
49
+ "models",
49
50
  // Flisk gives every service its own stack network and container name, so these change nothing it needs.
50
51
  "networks",
51
52
  "expose",
@@ -188,6 +189,26 @@ function volumeOf(entry, composeDir) {
188
189
  return undefined;
189
190
  return { source: resolved, target, ...(readOnly ? { readOnly } : {}), bind };
190
191
  }
192
+ /** Compose's project name: top-level `name`, else the directory name, lowercased with other characters dropped. */
193
+ function projectNameOf(root, absPath) {
194
+ const raw = isObj(root) && typeof root.name === "string" && !root.name.includes("$")
195
+ ? root.name
196
+ : basename(dirname(absPath));
197
+ const name = raw.toLowerCase().replace(/[^a-z0-9_-]/g, "");
198
+ return /^[a-z0-9]/.test(name) ? name : undefined;
199
+ }
200
+ /**
201
+ * The Docker name of a named volume, as `docker compose up` created it: its own `name:`, the key itself when it is
202
+ * `external`, else `<project>_<key>`. Undefined when the name uses Compose variables.
203
+ */
204
+ function dockerVolumeName(key, volumes, project) {
205
+ const def = isObj(volumes) ? volumes[key] : undefined;
206
+ if (isObj(def) && typeof def.name === "string")
207
+ return /^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(def.name) ? def.name : undefined;
208
+ if (isObj(def) && (def.external === true || isObj(def.external)))
209
+ return key;
210
+ return project ? `${project}_${key}` : undefined;
211
+ }
191
212
  function envNames(environment) {
192
213
  if (Array.isArray(environment)) {
193
214
  return environment
@@ -290,6 +311,20 @@ function healthProbeOf(healthcheck) {
290
311
  ...(retries ? { retries } : {}),
291
312
  };
292
313
  }
314
+ /** The models a service uses, joined to their top-level definitions. A model without a literal `model` is skipped. */
315
+ function modelsOf(uses, defined) {
316
+ const names = Array.isArray(uses) ? uses : isObj(uses) ? Object.keys(uses) : [];
317
+ const out = [];
318
+ for (const name of names) {
319
+ const def = isObj(defined) && typeof name === "string" ? defined[name] : undefined;
320
+ if (typeof name === "string" &&
321
+ isObj(def) &&
322
+ typeof def.model === "string" &&
323
+ /^[A-Za-z0-9_.-]+$/.test(name))
324
+ out.push({ name, model: def.model });
325
+ }
326
+ return out.length ? out : undefined;
327
+ }
293
328
  function healthPathOf(healthcheck) {
294
329
  if (!isObj(healthcheck) || !Array.isArray(healthcheck.test))
295
330
  return undefined;
@@ -343,6 +378,7 @@ registerDetector({
343
378
  if (!services || !isMap(doc.get("services", true))) {
344
379
  return { candidates, skips: [...skips, { ...base, reason: "no `services:` map" }] };
345
380
  }
381
+ const project = projectNameOf(root, absPath);
346
382
  for (const [name, raw] of Object.entries(services)) {
347
383
  if (!isObj(raw)) {
348
384
  skips.push({ ...base, reason: `service "${name}" is not a map` });
@@ -417,6 +453,7 @@ registerDetector({
417
453
  ? Object.keys(raw.depends_on)
418
454
  : [];
419
455
  const command = commandOf(raw.command);
456
+ const models = modelsOf(raw.models, isObj(root) ? root.models : undefined);
420
457
  const containerCommand = containerCommandOf(raw.command);
421
458
  const containerEntrypoint = containerCommandOf(raw.entrypoint);
422
459
  const healthProbe = healthProbeOf(raw.healthcheck);
@@ -461,8 +498,11 @@ registerDetector({
461
498
  const volumes = [];
462
499
  for (const entry of Array.isArray(raw.volumes) ? raw.volumes : []) {
463
500
  const volume = volumeOf(entry, composeDir);
501
+ const dockerName = volume && !volume.bind
502
+ ? dockerVolumeName(volume.source, isObj(root) ? root.volumes : undefined, project)
503
+ : undefined;
464
504
  if (volume)
465
- volumes.push(volume);
505
+ volumes.push(dockerName ? { ...volume, name: dockerName } : volume);
466
506
  else
467
507
  skips.push({
468
508
  ...base,
@@ -508,6 +548,7 @@ registerDetector({
508
548
  ...(command ? { command } : {}),
509
549
  ...(containerCommand ? { containerCommand } : {}),
510
550
  ...(containerEntrypoint ? { containerEntrypoint } : {}),
551
+ ...(models ? { models } : {}),
511
552
  ...(healthProbe ? { healthCheck: healthProbe } : {}),
512
553
  ...(selectedPort !== undefined ? { port: selectedPort } : {}),
513
554
  ...(healthCheckPath ? { healthCheckPath } : {}),
package/dist/merge.js CHANGED
@@ -73,6 +73,7 @@ function mergeGroup(name, dir, members, resolve) {
73
73
  ...firstOf(ordered, "restart"),
74
74
  ...firstOf(ordered, "containerCommand"),
75
75
  ...firstOf(ordered, "containerEntrypoint"),
76
+ ...firstOf(ordered, "models"),
76
77
  ...firstOf(ordered, "healthCheck"),
77
78
  ...(ordered.find((c) => c.sidecarOf)?.sidecarOf
78
79
  ? { sidecarOf: resolve(ordered.find((c) => c.sidecarOf)?.sidecarOf) }
package/dist/write.js CHANGED
@@ -271,14 +271,17 @@ export function planWrites(plan, force) {
271
271
  ...(secretNames.length ? { secrets: { required: secretNames } } : {}),
272
272
  ...(service.restart ? { restart: service.restart } : {}),
273
273
  ...(service.container ? { container: service.container } : {}),
274
+ ...(service.models ? { models: service.models } : {}),
274
275
  ...(extraPorts.length ? { extraPorts } : {}),
275
276
  ...(sidecarOf ? { sidecarOf } : {}),
276
277
  ...(volumes.length
277
278
  ? {
278
- volumes: volumes.map(({ source, target, readOnly }) => ({
279
+ // `name` keeps the Docker volume the Compose install already has, so its data is used with no copy.
280
+ volumes: volumes.map(({ source, target, readOnly, name }) => ({
279
281
  source,
280
282
  target,
281
283
  ...(readOnly ? { readOnly } : {}),
284
+ ...(name ? { name } : {}),
282
285
  })),
283
286
  }
284
287
  : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flisk-sh/flisk-import",
3
- "version": "0.8.0",
3
+ "version": "0.10.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",