@flisk-sh/flisk-import 0.2.3 → 0.3.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/LICENSE +1 -1
- package/README.md +47 -10
- package/dist/cli.js +8 -5
- package/dist/detectors/compose.js +23 -1
- package/dist/{tdk.js → flisk.js} +7 -6
- package/dist/format.js +1 -1
- package/dist/merge.js +4 -2
- package/dist/scan.js +4 -4
- package/dist/write.js +68 -12
- package/package.json +2 -2
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,24 +1,61 @@
|
|
|
1
|
-
#
|
|
1
|
+
# flisk import
|
|
2
2
|
|
|
3
|
-
Import the services in a repo's root `docker-compose.yml` into [
|
|
3
|
+
Import the services in a repo's root `docker-compose.yml` into [Flisk](https://github.com/flisk-sh/flisk) as `service.json` files. This is the Compose first tier of flisk-sh/flisk#511.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
flisk import . --dry-run # plan only
|
|
7
|
+
flisk import . # plan, then ask before writing
|
|
8
|
+
flisk import . --yes # write service.json files and project setup
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## What to expect
|
|
12
12
|
|
|
13
13
|
- **Reads one file:** the root `docker-compose.yml`. Not `compose.yaml`, override files, nested Compose files, Dockerfiles on their own, `package.json`, or Procfiles.
|
|
14
|
-
- **Writes:** one bring-your-own `service.json` per Compose service, plus `.
|
|
15
|
-
- **Keeps:** the build context and Dockerfile (or the image), the container port, `depends_on`, and an HTTP health path. Databases and brokers are private, with no proxy route.
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
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 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
|
+
- **Environment:** names go into `service.json` as `secrets.required`; literal values are copied to the project `.env`, which is added to `.gitignore`. Values in `.env` already are kept, and `${VAR}` values are left for you.
|
|
17
|
+
- **Drops, and says so:** volumes, `command`, `env_file` and every other untranslated key; services with `profiles`, `extends`, an out-of-tree build or a `${VAR}` image.
|
|
18
|
+
- **Does not start anything.** Run `flisk up` with Flisk core **1.4.11 or later**. The importer does not check the core version.
|
|
18
19
|
- **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.`).
|
|
19
20
|
|
|
20
|
-
See [docs/import.md](docs/import.md) for the full mapping and limits. `
|
|
21
|
+
See [docs/import.md](docs/import.md) for the full mapping and limits. `flisk import` is the core CLI command backed by this package.
|
|
21
22
|
|
|
22
23
|
```bash
|
|
23
24
|
bun install && bun run build && bun run test
|
|
24
25
|
```
|
|
26
|
+
|
|
27
|
+
## Publishing
|
|
28
|
+
|
|
29
|
+
`flisk-import` is published to npmjs.org by the workflow in
|
|
30
|
+
[`.github/workflows/publish-npm.yml`](.github/workflows/publish-npm.yml), triggered by a
|
|
31
|
+
GitHub release. It uses **npm trusted publishing (OIDC)**, so no long-lived npm token
|
|
32
|
+
lives in this repository. The package is published under two names: the unscoped
|
|
33
|
+
`flisk-import` and the scoped `@flisk-sh/flisk-import` (the same archive with the name
|
|
34
|
+
rewritten in the packed manifest).
|
|
35
|
+
|
|
36
|
+
### One-time owner setup
|
|
37
|
+
|
|
38
|
+
A trusted publisher has to exist on npm before the first OIDC publish succeeds, and it is
|
|
39
|
+
configured per package name:
|
|
40
|
+
|
|
41
|
+
1. Open the package on npmjs.com → **Settings** → **Trusted Publisher** → **GitHub Actions**
|
|
42
|
+
2. Fill in:
|
|
43
|
+
- Organization: `flisk-sh`
|
|
44
|
+
- Repository: `flisk-import`
|
|
45
|
+
- Workflow filename: `publish-npm.yml`
|
|
46
|
+
- Allowed actions: `npm publish`
|
|
47
|
+
3. Repeat for `@flisk-sh/flisk-import` once that package exists
|
|
48
|
+
|
|
49
|
+
The same thing from the command line, for either name:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm trust github flisk-import --file publish-npm.yml --repo flisk-sh/flisk-import --allow-publish
|
|
53
|
+
npm trust list flisk-import
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### The first version
|
|
57
|
+
|
|
58
|
+
A package's first version cannot be published through OIDC — npm has no trusted publisher
|
|
59
|
+
for a name that does not exist yet. Version 0.2.2 was therefore published once with a
|
|
60
|
+
token; every version after that publishes over OIDC from the release workflow.
|
|
61
|
+
|
package/dist/cli.js
CHANGED
|
@@ -5,9 +5,9 @@ import { createInterface } from "node:readline/promises";
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import "./detectors/index.js";
|
|
7
7
|
import { ImportError } from "./errors.js";
|
|
8
|
+
import { MIN_CORE_VERSION } from "./flisk.js";
|
|
8
9
|
import { formatPlan } from "./format.js";
|
|
9
10
|
import { buildPlan } from "./plan.js";
|
|
10
|
-
import { MIN_CORE_VERSION } from "./tdk.js";
|
|
11
11
|
import { applyWrites, manifestRelPath, planWrites } from "./write.js";
|
|
12
12
|
async function confirm(message) {
|
|
13
13
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -46,7 +46,7 @@ const program = new Command("flisk-import")
|
|
|
46
46
|
return void console.log("Nothing to write: every service.json already exists.");
|
|
47
47
|
}
|
|
48
48
|
if (!options.yes &&
|
|
49
|
-
!(await confirm(`Write ${pending.length} service.json file(s)${writes.projectConfig ? " and create .
|
|
49
|
+
!(await confirm(`Write ${pending.length} service.json file(s)${writes.projectConfig ? " and create .flisk/project.json" : ""}?`))) {
|
|
50
50
|
console.log("Nothing written.");
|
|
51
51
|
process.exitCode = 2;
|
|
52
52
|
return;
|
|
@@ -55,11 +55,14 @@ const program = new Command("flisk-import")
|
|
|
55
55
|
for (const item of written) {
|
|
56
56
|
console.log(` wrote ${relative(process.cwd(), item.path) || manifestRelPath(plan, item)}`);
|
|
57
57
|
}
|
|
58
|
-
console.log(`\nWrote ${written.length} service.json file(s)${writes.projectConfig ? " and created .
|
|
58
|
+
console.log(`\nWrote ${written.length} service.json file(s)${writes.projectConfig ? " and created .flisk/project.json" : ""}.`);
|
|
59
59
|
const envFile = relative(process.cwd(), join(writes.projectRoot, ".env")) || ".env";
|
|
60
60
|
console.log("\nNext:");
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
const copied = Object.keys(writes.env.add);
|
|
62
|
+
if (copied.length)
|
|
63
|
+
console.log(` - Copied ${copied.length} environment value(s) from docker-compose.yml to ${envFile} (gitignored).`);
|
|
64
|
+
if (writes.items.some((item) => item.notes.some((note) => note.startsWith("no literal value"))))
|
|
65
|
+
console.log(` - Add the values listed above as missing to ${envFile}.`);
|
|
63
66
|
console.log(` - Run \`flisk up\` with Flisk core ${MIN_CORE_VERSION} or later. The import did not start anything.`);
|
|
64
67
|
});
|
|
65
68
|
try {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, isAbsolute, join, normalize } from "node:path/posix";
|
|
3
3
|
import { isAlias, isMap, parseDocument, visit } from "yaml";
|
|
4
|
+
import { HEALTH_CHECK_PATH_PATTERN } from "../flisk.js";
|
|
4
5
|
import { registerDetector } from "../registry.js";
|
|
5
|
-
import { HEALTH_CHECK_PATH_PATTERN } from "../tdk.js";
|
|
6
6
|
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
7
7
|
/** Container-side port of one `ports:` entry, in short or long syntax. */
|
|
8
8
|
function containerPort(entry) {
|
|
@@ -41,6 +41,26 @@ function envNames(environment) {
|
|
|
41
41
|
}
|
|
42
42
|
return isObj(environment) ? Object.keys(environment) : [];
|
|
43
43
|
}
|
|
44
|
+
/** Literal environment values; interpolated ones (`${VAR}`) and empty entries are left for the user. */
|
|
45
|
+
function envValues(environment) {
|
|
46
|
+
const pairs = Array.isArray(environment)
|
|
47
|
+
? environment
|
|
48
|
+
.filter((e) => typeof e === "string" && e.includes("="))
|
|
49
|
+
.map((e) => [e.slice(0, e.indexOf("=")), e.slice(e.indexOf("=") + 1)])
|
|
50
|
+
: isObj(environment)
|
|
51
|
+
? Object.entries(environment)
|
|
52
|
+
: [];
|
|
53
|
+
const out = {};
|
|
54
|
+
for (const [key, raw] of pairs) {
|
|
55
|
+
if (raw === null || raw === undefined || typeof raw === "object")
|
|
56
|
+
continue;
|
|
57
|
+
const value = String(raw);
|
|
58
|
+
if (value.includes("${") || value.includes("\n"))
|
|
59
|
+
continue;
|
|
60
|
+
out[key] = value;
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
44
64
|
function commandOf(command) {
|
|
45
65
|
if (typeof command === "string")
|
|
46
66
|
return command;
|
|
@@ -176,6 +196,7 @@ registerDetector({
|
|
|
176
196
|
: [];
|
|
177
197
|
const command = commandOf(raw.command);
|
|
178
198
|
const envKeys = envNames(raw.environment);
|
|
199
|
+
const env = envValues(raw.environment);
|
|
179
200
|
const foundHealthPath = healthPathOf(raw.healthcheck);
|
|
180
201
|
const healthCheckPath = foundHealthPath && HEALTH_CHECK_PATH_PATTERN.test(foundHealthPath)
|
|
181
202
|
? foundHealthPath
|
|
@@ -208,6 +229,7 @@ registerDetector({
|
|
|
208
229
|
...(healthCheckPath ? { healthCheckPath } : {}),
|
|
209
230
|
exposeViaProxy,
|
|
210
231
|
...(envKeys.length ? { envKeys } : {}),
|
|
232
|
+
...(Object.keys(env).length ? { env } : {}),
|
|
211
233
|
...(dependsOn.length ? { dependsOn } : {}),
|
|
212
234
|
confidence: "high",
|
|
213
235
|
sources: [base],
|
package/dist/{tdk.js → flisk.js}
RENAMED
|
@@ -4,11 +4,12 @@ import { dirname, join, resolve } from "node:path";
|
|
|
4
4
|
export const SERVICE_MANIFEST_SCHEMA_VERSION = 1;
|
|
5
5
|
export const SERVICE_MANIFEST_SCHEMA_URL = "https://flisk.sh/schema.service.json";
|
|
6
6
|
/**
|
|
7
|
-
* Oldest Flisk core that starts an imported project: 1.3.104 added `buildContext`,
|
|
8
|
-
* (flisk#1018) bootstraps a project that has `.
|
|
9
|
-
*
|
|
7
|
+
* Oldest Flisk core that starts an imported project: 1.3.104 added `buildContext`, 1.3.145
|
|
8
|
+
* (flisk#1018) bootstraps a project that has `.flisk/project.json` but no Tiltfile yet, and the
|
|
9
|
+
* release after 1.4.10 (flisk#2646) accepts a bring-your-own port outside 4000-5999 and two-letter
|
|
10
|
+
* names, which this importer now writes. Stated, not checked: it does not read the installed version.
|
|
10
11
|
*/
|
|
11
|
-
export const MIN_CORE_VERSION = "1.
|
|
12
|
+
export const MIN_CORE_VERSION = "1.4.11";
|
|
12
13
|
/** Flisk's bring-your-own port range. */
|
|
13
14
|
export const BYO_PORT_RANGE = { min: 4000, max: 5999, label: "4000-5999" };
|
|
14
15
|
/**
|
|
@@ -26,11 +27,11 @@ export function validateName(label, name) {
|
|
|
26
27
|
error: `${label} "${name}" must use lowercase letters, numbers, and hyphens only`,
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
|
-
/** Nearest directory at or above `start` holding `.
|
|
30
|
+
/** Nearest directory at or above `start` holding `.flisk/project.json`. */
|
|
30
31
|
export function findProjectRoot(start) {
|
|
31
32
|
let dir = resolve(start);
|
|
32
33
|
while (true) {
|
|
33
|
-
if (existsSync(join(dir, ".
|
|
34
|
+
if (existsSync(join(dir, ".flisk", "project.json")))
|
|
34
35
|
return dir;
|
|
35
36
|
const parent = dirname(dir);
|
|
36
37
|
if (parent === dir)
|
package/dist/format.js
CHANGED
|
@@ -10,7 +10,7 @@ export function formatPlan(plan, writes) {
|
|
|
10
10
|
const jobs = plan.services.filter((s) => s.kind === "job").length;
|
|
11
11
|
out.push(`Found ${plan.services.length - jobs} service(s) and ${jobs} job(s) in ${plan.root}`, `Read only: ${plan.files.join(", ")}. Lines below name what was not translated.`, "");
|
|
12
12
|
if (writes.projectConfig)
|
|
13
|
-
out.push(` .
|
|
13
|
+
out.push(` .flisk/project.json [create Flisk project]`, "");
|
|
14
14
|
for (const service of plan.services) {
|
|
15
15
|
const item = writes.items.find((i) => i.service === service);
|
|
16
16
|
out.push(` ${service.name} (${service.kind}${service.dir ? `, ${service.dir}/` : ""})`);
|
package/dist/merge.js
CHANGED
|
@@ -8,10 +8,11 @@ function slug(raw) {
|
|
|
8
8
|
}
|
|
9
9
|
export function toResourceName(raw) {
|
|
10
10
|
let name = slug(raw) || "svc";
|
|
11
|
-
// Resource names must start with a letter and be at least
|
|
11
|
+
// Resource names must start with a letter and be at least 2 characters (service schema), so a
|
|
12
|
+
// Compose name such as `db` is kept and stays the container's hostname.
|
|
12
13
|
if (!/^[a-z]/.test(name))
|
|
13
14
|
name = `svc-${name}`;
|
|
14
|
-
if (name.length <
|
|
15
|
+
if (name.length < 2)
|
|
15
16
|
name = `${name}-svc`;
|
|
16
17
|
return name;
|
|
17
18
|
}
|
|
@@ -57,6 +58,7 @@ function mergeGroup(name, dir, members, resolve) {
|
|
|
57
58
|
dir,
|
|
58
59
|
kind: ordered[0].kind,
|
|
59
60
|
envKeys: [...new Set(ordered.flatMap((c) => c.envKeys ?? []))].sort(),
|
|
61
|
+
env: Object.assign({}, ...[...ordered].reverse().map((c) => c.env ?? {})),
|
|
60
62
|
dependsOn: [...new Set(ordered.flatMap((c) => (c.dependsOn ?? []).map(resolve)))].sort(),
|
|
61
63
|
exposeViaProxy: ordered.find((c) => c.exposeViaProxy !== undefined)?.exposeViaProxy,
|
|
62
64
|
procfileCommands: ordered.flatMap((c) => c.sources.some((source) => source.detector === "procfile") && c.command ? [c.command] : []),
|
package/dist/scan.js
CHANGED
|
@@ -8,7 +8,7 @@ export const HARD_SKIPS = new Set([
|
|
|
8
8
|
"dist",
|
|
9
9
|
".autogenerated",
|
|
10
10
|
// Output of `flisk project`: never a source of services.
|
|
11
|
-
".
|
|
11
|
+
".flisk",
|
|
12
12
|
"shared-platform-engineering",
|
|
13
13
|
]);
|
|
14
14
|
export const MAX_DEPTH = 8;
|
|
@@ -84,8 +84,8 @@ export function scanTree(root, limits = { depth: MAX_DEPTH, files: MAX_FILES })
|
|
|
84
84
|
let depthCapped = false;
|
|
85
85
|
let fileCapped = false;
|
|
86
86
|
// In a Flisk project, `services/` holds manifests and generated compose files, not sources.
|
|
87
|
-
const
|
|
88
|
-
if (
|
|
87
|
+
const isFliskProject = existsSync(join(root, ".flisk", "project.json"));
|
|
88
|
+
if (isFliskProject)
|
|
89
89
|
notes.push("Flisk project detected: services/ was not scanned");
|
|
90
90
|
const walk = (rel, depth, inherited) => {
|
|
91
91
|
const abs = join(root, rel);
|
|
@@ -106,7 +106,7 @@ export function scanTree(root, limits = { depth: MAX_DEPTH, files: MAX_FILES })
|
|
|
106
106
|
if (entry.isDirectory()) {
|
|
107
107
|
if (HARD_SKIPS.has(entry.name) || isIgnored(rules, entryRel, entry.name, true))
|
|
108
108
|
continue;
|
|
109
|
-
if (
|
|
109
|
+
if (isFliskProject && entryRel === "services")
|
|
110
110
|
continue;
|
|
111
111
|
if (depth >= limits.depth) {
|
|
112
112
|
depthCapped = true;
|
package/dist/write.js
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
3
3
|
import { ImportError } from "./errors.js";
|
|
4
|
+
import { BYO_PORT_RANGE, findProjectRoot, IMAGE_PATTERN, SERVICE_MANIFEST_SCHEMA_URL, SERVICE_MANIFEST_SCHEMA_VERSION, validateName, } from "./flisk.js";
|
|
4
5
|
import { toResourceName } from "./merge.js";
|
|
5
6
|
import { usedManifestPorts } from "./plan.js";
|
|
6
|
-
|
|
7
|
+
/** Secret names the service schema accepts (`secrets.required`). */
|
|
8
|
+
const ENV_NAME = /^[A-Z_][A-Z0-9_]*$/;
|
|
9
|
+
function readEnvKeys(path) {
|
|
10
|
+
if (!existsSync(path))
|
|
11
|
+
return new Set();
|
|
12
|
+
return new Set(readFileSync(path, "utf8")
|
|
13
|
+
.split(/\r?\n/)
|
|
14
|
+
.map((line) => line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/)?.[1])
|
|
15
|
+
.filter((key) => key !== undefined));
|
|
16
|
+
}
|
|
17
|
+
/** A .env value, quoted when it holds characters a dotenv parser would split or strip. */
|
|
18
|
+
function envLine(key, value) {
|
|
19
|
+
return /^[A-Za-z0-9_./:@+-]*$/.test(value)
|
|
20
|
+
? `${key}=${value}`
|
|
21
|
+
: `${key}=${JSON.stringify(value)}`;
|
|
22
|
+
}
|
|
7
23
|
function existingPort(path) {
|
|
8
24
|
try {
|
|
9
25
|
const port = JSON.parse(readFileSync(path, "utf8")).port;
|
|
@@ -29,7 +45,7 @@ export function planWrites(plan, force) {
|
|
|
29
45
|
]);
|
|
30
46
|
}
|
|
31
47
|
const servicesDir = join(projectRoot, "services");
|
|
32
|
-
const projectConfigPath = join(projectRoot, ".
|
|
48
|
+
const projectConfigPath = join(projectRoot, ".flisk", "project.json");
|
|
33
49
|
const projectConfig = existsSync(projectConfigPath)
|
|
34
50
|
? undefined
|
|
35
51
|
: {
|
|
@@ -58,6 +74,9 @@ export function planWrites(plan, force) {
|
|
|
58
74
|
overrides: {},
|
|
59
75
|
}, null, 2)}\n`,
|
|
60
76
|
};
|
|
77
|
+
const envPath = join(projectRoot, ".env");
|
|
78
|
+
const envKept = readEnvKeys(envPath);
|
|
79
|
+
const envAdd = {};
|
|
61
80
|
const used = usedManifestPorts(servicesDir);
|
|
62
81
|
const items = [];
|
|
63
82
|
const rejected = [];
|
|
@@ -97,8 +116,9 @@ export function planWrites(plan, force) {
|
|
|
97
116
|
// Never written, so no port is needed; keep the one on disk for the plan.
|
|
98
117
|
port = existingPort(path) ?? BYO_PORT_RANGE.min;
|
|
99
118
|
}
|
|
100
|
-
else if (service.port !== undefined
|
|
101
|
-
//
|
|
119
|
+
else if (service.port !== undefined) {
|
|
120
|
+
// A bring-your-own port is the port inside its own container (Traefik reaches it on the stack
|
|
121
|
+
// network; nothing is published on the host), so the Compose port is kept as written.
|
|
102
122
|
port = service.port;
|
|
103
123
|
}
|
|
104
124
|
else if (keep !== undefined && free(keep)) {
|
|
@@ -112,11 +132,8 @@ export function planWrites(plan, force) {
|
|
|
112
132
|
}
|
|
113
133
|
}
|
|
114
134
|
}
|
|
115
|
-
if (status !== "exists" &&
|
|
116
|
-
|
|
117
|
-
port !== undefined &&
|
|
118
|
-
port !== service.port)
|
|
119
|
-
notes.push(`Compose port ${service.port} is outside ${BYO_PORT_RANGE.label} or already used; assigned ${port}. The container must listen on $PORT (${port}) or it will not answer`);
|
|
135
|
+
if (status !== "exists" && service.port === undefined && !isPrivate && port !== undefined)
|
|
136
|
+
notes.push(`port ${port} assigned: Compose gives no single container port; the container must listen on $PORT (${port})`);
|
|
120
137
|
if (port === undefined) {
|
|
121
138
|
rejected.push({ name: service.name, reason: `no free port in ${BYO_PORT_RANGE.label}` });
|
|
122
139
|
continue;
|
|
@@ -135,8 +152,23 @@ export function planWrites(plan, force) {
|
|
|
135
152
|
notes.push("no health path: the service does not serve HTTP (non-HTTP image or job)");
|
|
136
153
|
if (!isPrivate && !nonHttp && !service.healthCheckPath)
|
|
137
154
|
notes.push("health path / is assumed: no HTTP health path could be read from Compose. Traefik routes only while it answers 2xx or 3xx");
|
|
138
|
-
|
|
139
|
-
|
|
155
|
+
// Names go into service.json as secrets, so Flisk injects them; values go to the gitignored project .env.
|
|
156
|
+
const secretNames = service.envKeys.filter((key) => ENV_NAME.test(key));
|
|
157
|
+
const skippedNames = service.envKeys.filter((key) => !ENV_NAME.test(key));
|
|
158
|
+
if (skippedNames.length)
|
|
159
|
+
notes.push(`environment names not written (Flisk accepts A-Z, 0-9 and _): ${skippedNames.join(", ")}`);
|
|
160
|
+
const missing = [];
|
|
161
|
+
for (const key of secretNames) {
|
|
162
|
+
const value = service.env[key];
|
|
163
|
+
if (envKept.has(key) || key in envAdd)
|
|
164
|
+
continue;
|
|
165
|
+
if (value === undefined)
|
|
166
|
+
missing.push(key);
|
|
167
|
+
else
|
|
168
|
+
envAdd[key] = value;
|
|
169
|
+
}
|
|
170
|
+
if (missing.length)
|
|
171
|
+
notes.push(`no literal value in docker-compose.yml for ${missing.join(", ")}; add it to the project .env`);
|
|
140
172
|
if (service.dockerfile && service.image)
|
|
141
173
|
notes.push(`image "${service.image}" is not written: with build and image, Flisk builds from ${service.dockerfile} and does not pull the tag`);
|
|
142
174
|
// With both build and image, Compose builds the Dockerfile and tags the result as `image`.
|
|
@@ -158,10 +190,18 @@ export function planWrites(plan, force) {
|
|
|
158
190
|
...source,
|
|
159
191
|
...(isPrivate ? { exposeViaProxy: false } : {}),
|
|
160
192
|
...(dependsOn.length ? { dependsOn } : {}),
|
|
193
|
+
...(secretNames.length ? { secrets: { required: secretNames } } : {}),
|
|
161
194
|
};
|
|
162
195
|
items.push({ service, path, manifest, status, notes });
|
|
163
196
|
}
|
|
164
|
-
return {
|
|
197
|
+
return {
|
|
198
|
+
projectRoot,
|
|
199
|
+
stack,
|
|
200
|
+
items,
|
|
201
|
+
projectConfig,
|
|
202
|
+
rejected,
|
|
203
|
+
env: { path: envPath, add: envAdd },
|
|
204
|
+
};
|
|
165
205
|
}
|
|
166
206
|
export function applyWrites(writes) {
|
|
167
207
|
const written = [];
|
|
@@ -176,8 +216,24 @@ export function applyWrites(writes) {
|
|
|
176
216
|
writeFileSync(item.path, `${JSON.stringify(item.manifest, null, 2)}\n`);
|
|
177
217
|
written.push(item);
|
|
178
218
|
}
|
|
219
|
+
const keys = Object.keys(writes.env.add);
|
|
220
|
+
if (keys.length) {
|
|
221
|
+
const existing = existsSync(writes.env.path) ? readFileSync(writes.env.path, "utf8") : "";
|
|
222
|
+
const lines = keys.map((key) => envLine(key, writes.env.add[key]));
|
|
223
|
+
const block = `${existing && !existing.endsWith("\n") ? "\n" : ""}# From docker-compose.yml (flisk import)\n${lines.join("\n")}\n`;
|
|
224
|
+
writeFileSync(writes.env.path, existing + block);
|
|
225
|
+
ensureGitignored(writes.projectRoot, ".env");
|
|
226
|
+
}
|
|
179
227
|
return written;
|
|
180
228
|
}
|
|
181
229
|
export function manifestRelPath(plan, item) {
|
|
182
230
|
return relative(plan.root, item.path).split("\\").join("/");
|
|
183
231
|
}
|
|
232
|
+
/** Keeps the project .env out of git: it now holds values copied from docker-compose.yml. */
|
|
233
|
+
function ensureGitignored(projectRoot, entry) {
|
|
234
|
+
const path = join(projectRoot, ".gitignore");
|
|
235
|
+
const content = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
236
|
+
if (content.split(/\r?\n/).some((line) => line.trim() === entry || line.trim() === `/${entry}`))
|
|
237
|
+
return;
|
|
238
|
+
writeFileSync(path, `${content}${content && !content.endsWith("\n") ? "\n" : ""}${entry}\n`);
|
|
239
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flisk-sh/flisk-import",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Import services from a repository's root docker-compose.yml into
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Import services from a repository's root docker-compose.yml into Flisk service.json files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|