Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/public-masks-kiss.md
Original file line number Diff line number Diff line change
@@ -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.
35 changes: 34 additions & 1 deletion __tests__/commands/transfer/wizard/envWriter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
49 changes: 31 additions & 18 deletions src/commands/transfer/wizard/envWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>();
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<void> {
Expand Down
25 changes: 14 additions & 11 deletions templates/projects/example/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Loading