diff --git a/.changeset/public-masks-kiss.md b/.changeset/public-masks-kiss.md new file mode 100644 index 0000000..f97be8f --- /dev/null +++ b/.changeset/public-masks-kiss.md @@ -0,0 +1,5 @@ +--- +"@webiny/data-transfer": patch +--- + +Fix `.env` population for older scaffolded projects: `writeEnv` now supports plain `KEY=value` `.env.example` files by uncommenting commented-out known keys and appending missing ones. Config template auto-enables OpenSearch when the env vars are set — no manual uncommenting needed. diff --git a/__tests__/commands/transfer/wizard/envWriter.test.ts b/__tests__/commands/transfer/wizard/envWriter.test.ts index d24c3f0..f6018d6 100644 --- a/__tests__/commands/transfer/wizard/envWriter.test.ts +++ b/__tests__/commands/transfer/wizard/envWriter.test.ts @@ -176,7 +176,40 @@ describe("writeEnv", () => { await writeEnv(tmpDir, values); const content = await readFile(join(tmpDir, ".env"), "utf8"); - expect(content).toBe("TARGET_OS_INDEX_PREFIX=\n"); + expect(content).toContain("TARGET_OS_INDEX_PREFIX=\n"); + expect(content).not.toContain("old-prefix"); + }); + + it("plain format uncomments commented-out known keys and fills them", async () => { + const plain = [ + "SOURCE_REGION=eu-central-1", + "# SOURCE_OS_TABLE=", + "# TARGET_OS_TABLE=", + "# TARGET_OS_ENDPOINT=" + ].join("\n"); + await writeFile(join(tmpDir, ".env.example"), plain); + + await writeEnv(tmpDir, SAMPLE_VALUES); + + const content = await readFile(join(tmpDir, ".env"), "utf8"); + expect(content).toContain("SOURCE_OS_TABLE=wby-source-es"); + expect(content).toContain("TARGET_OS_TABLE=wby-target-os"); + expect(content).toContain("TARGET_OS_ENDPOINT=search-target.us-east-1.es.amazonaws.com"); + expect(content).not.toContain("# SOURCE_OS_TABLE"); + expect(content).not.toContain("# TARGET_OS_TABLE"); + }); + + it("plain format appends missing known keys at the end", async () => { + const plain = "SOURCE_REGION=eu-central-1\nSEGMENTS=4\n"; + await writeFile(join(tmpDir, ".env.example"), plain); + + await writeEnv(tmpDir, SAMPLE_VALUES); + + const content = await readFile(join(tmpDir, ".env"), "utf8"); + expect(content).toContain("SOURCE_REGION=eu-central-1"); + expect(content).toContain("SEGMENTS=8"); + expect(content).toContain("TARGET_OS_ENDPOINT=search-target.us-east-1.es.amazonaws.com"); + expect(content).toContain("SOURCE_ACCOUNT_ID=111111111111"); }); it("uses built-in template when .env.example is absent", async () => { diff --git a/src/commands/transfer/wizard/envWriter.ts b/src/commands/transfer/wizard/envWriter.ts index 3bb0d1c..9d8adc2 100644 --- a/src/commands/transfer/wizard/envWriter.ts +++ b/src/commands/transfer/wizard/envWriter.ts @@ -79,25 +79,38 @@ function substituteTokens(template: string, values: EnvValues): string { } function substituteEnvLines(content: string, values: EnvValues): string { - return content - .split("\n") - .map(line => { - const trimmed = line.trimStart(); - if (!trimmed || trimmed.startsWith("#")) { - return line; - } - const eqIndex = trimmed.indexOf("="); - if (eqIndex === -1) { - return line; - } - const key = trimmed.slice(0, eqIndex).trim(); - const envKey = TOKEN_MAP[key]; - if (envKey) { - return `${key}=${String(values[envKey])}`; - } + const seen = new Set(); + const lines = content.split("\n").map(line => { + const trimmed = line.trimStart(); + if (!trimmed) { return line; - }) - .join("\n"); + } + + const commented = trimmed.startsWith("#"); + const stripped = commented ? trimmed.slice(1).trimStart() : trimmed; + const eqIndex = stripped.indexOf("="); + if (eqIndex === -1) { + return line; + } + + const key = stripped.slice(0, eqIndex).trim(); + const envKey = TOKEN_MAP[key]; + if (envKey) { + seen.add(key); + return `${key}=${String(values[envKey])}`; + } + return line; + }); + + const missing = Object.entries(TOKEN_MAP).filter(([key]) => !seen.has(key)); + if (missing.length > 0) { + lines.push(""); + for (const [key, envKey] of missing) { + lines.push(`${key}=${String(values[envKey])}`); + } + } + + return lines.join("\n"); } export async function writeEnv(projectDir: string, values: EnvValues): Promise { diff --git a/templates/projects/example/config.ts b/templates/projects/example/config.ts index e7e3077..5cc6fc0 100644 --- a/templates/projects/example/config.ts +++ b/templates/projects/example/config.ts @@ -8,27 +8,30 @@ import { loadEnv(import.meta.url); +const sourceOsTable = fromEnv("SOURCE_OS_TABLE", null); +const targetOsTable = fromEnv("TARGET_OS_TABLE", null); + export default createConfig({ source: { region: fromEnv("SOURCE_REGION", "eu-central-1"), credentials: fromAwsProfile({ profile: fromEnv("SOURCE_PROFILE", "default") }), dynamodb: { tableName: fromEnv("SOURCE_DDB_TABLE") }, - s3: { bucket: fromEnv("SOURCE_S3_BUCKET") } - // Uncomment if your Webiny project uses OpenSearch: - // opensearch: { tableName: fromEnv("SOURCE_OS_TABLE") } + s3: { bucket: fromEnv("SOURCE_S3_BUCKET") }, + opensearch: sourceOsTable ? { tableName: sourceOsTable } : null }, target: { region: fromEnv("TARGET_REGION", "eu-central-1"), credentials: fromAwsProfile({ profile: fromEnv("TARGET_PROFILE", "default") }), dynamodb: { tableName: fromEnv("TARGET_DDB_TABLE") }, - s3: { bucket: fromEnv("TARGET_S3_BUCKET") } - // Uncomment if your Webiny project uses OpenSearch: - // opensearch: { - // endpoint: fromEnv("TARGET_OS_ENDPOINT"), - // tableName: fromEnv("TARGET_OS_TABLE"), - // service: "opensearch", - // indexPrefix: fromEnv("TARGET_OS_INDEX_PREFIX", "") - // } + s3: { bucket: fromEnv("TARGET_S3_BUCKET") }, + opensearch: targetOsTable + ? { + endpoint: fromEnv("TARGET_OS_ENDPOINT"), + tableName: targetOsTable, + service: "opensearch", + indexPrefix: fromEnv("TARGET_OS_INDEX_PREFIX", "") + } + : null }, pipeline: { segments: numberFromEnv("SEGMENTS", 4),