@envmanager-cli/cli 0.1.10 → 0.2.1
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/dist/bin/envmanager.js +134 -24
- package/dist/bin/envmanager.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/envmanager.js
CHANGED
|
@@ -615,7 +615,9 @@ var ConfigSchema = z.object({
|
|
|
615
615
|
api_url: z.string().url().optional(),
|
|
616
616
|
format: z.enum(EXPORT_FORMATS).optional(),
|
|
617
617
|
k8s_namespace: z.string().optional(),
|
|
618
|
-
k8s_name: z.string().optional()
|
|
618
|
+
k8s_name: z.string().optional(),
|
|
619
|
+
tags: z.array(z.string()).optional(),
|
|
620
|
+
service: z.string().optional()
|
|
619
621
|
});
|
|
620
622
|
var CONFIG_FILENAMES = ["envmanager.json", ".envmanagerrc"];
|
|
621
623
|
function findConfigFile(startDir = process.cwd()) {
|
|
@@ -725,6 +727,15 @@ async function detectOrgFromProject(projectInput, client, memberships) {
|
|
|
725
727
|
if (data && data.length === 1) return data[0].organization_id;
|
|
726
728
|
return null;
|
|
727
729
|
}
|
|
730
|
+
async function resolveServiceId(serviceName, client, projectId) {
|
|
731
|
+
const { data, error } = await client.from("services").select("id").eq("project_id", projectId).ilike("name", serviceName).single();
|
|
732
|
+
if (error || !data) {
|
|
733
|
+
const { data: allServices } = await client.from("services").select("name").eq("project_id", projectId).order("sort_order");
|
|
734
|
+
const available = allServices?.map((s) => s.name).join(", ") || "none";
|
|
735
|
+
throw new Error(`Service "${serviceName}" not found in project. Available services: ${available}`);
|
|
736
|
+
}
|
|
737
|
+
return data.id;
|
|
738
|
+
}
|
|
728
739
|
|
|
729
740
|
// src/lib/variable-references.ts
|
|
730
741
|
var REFERENCE_PATTERN = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
|
|
@@ -885,7 +896,7 @@ function resolveAll(variables) {
|
|
|
885
896
|
}
|
|
886
897
|
|
|
887
898
|
// src/commands/pull.ts
|
|
888
|
-
var pullCommand = new Command4("pull").description("Pull environment variables from EnvManager to local .env file").option("--org <name>", "Organization name (required if you belong to multiple)").option("-e, --environment <name>", 'Environment name (default: from config or "development")').option("-p, --project <id>", "Project ID (default: from config)").option("-o, --output <file>", "Output file path (default: .env)").option("--no-secrets", "Exclude secret values (will be empty)").option("-f, --force", "Overwrite existing file without prompting").option("-r, --resolve-references", "Resolve ${VAR} references to their values").option("-F, --include-fallbacks", "Include fallback values for empty variables").option("-s, --show-sources", "Show value source as inline comments").option("--format <type>", `Export format (${EXPORT_FORMATS.join(", ")})`).option("--k8s-namespace <ns>", 'Kubernetes namespace (default: "default")').option("--k8s-name <name>", "Kubernetes resource name").action(async (options) => {
|
|
899
|
+
var pullCommand = new Command4("pull").description("Pull environment variables from EnvManager to local .env file").option("--org <name>", "Organization name (required if you belong to multiple)").option("-e, --environment <name>", 'Environment name (default: from config or "development")').option("-p, --project <id>", "Project ID (default: from config)").option("-o, --output <file>", "Output file path (default: .env)").option("--no-secrets", "Exclude secret values (will be empty)").option("-f, --force", "Overwrite existing file without prompting").option("-r, --resolve-references", "Resolve ${VAR} references to their values").option("-F, --include-fallbacks", "Include fallback values for empty variables").option("-s, --show-sources", "Show value source as inline comments").option("--format <type>", `Export format (${EXPORT_FORMATS.join(", ")})`).option("--k8s-namespace <ns>", 'Kubernetes namespace (default: "default")').option("--k8s-name <name>", "Kubernetes resource name").option("--tag <tags...>", "Filter by tags (untagged variables always included)").option("--service <name>", "Filter by service name").action(async (options) => {
|
|
889
900
|
const spinner = ora3("Connecting to EnvManager...").start();
|
|
890
901
|
try {
|
|
891
902
|
const config = loadConfig();
|
|
@@ -923,6 +934,16 @@ var pullCommand = new Command4("pull").description("Pull environment variables f
|
|
|
923
934
|
spinner.fail(error instanceof Error ? error.message : "Failed to resolve project");
|
|
924
935
|
process.exit(1);
|
|
925
936
|
}
|
|
937
|
+
const serviceName = options.service || config?.service;
|
|
938
|
+
let serviceId;
|
|
939
|
+
if (serviceName) {
|
|
940
|
+
try {
|
|
941
|
+
serviceId = await resolveServiceId(serviceName, client, projectId);
|
|
942
|
+
} catch (error) {
|
|
943
|
+
spinner.fail(error instanceof Error ? error.message : "Failed to resolve service");
|
|
944
|
+
process.exit(1);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
926
947
|
spinner.text = "Fetching environment...";
|
|
927
948
|
const { data: environments, error: envError } = await client.from("environments").select("id, name").eq("project_id", projectId).ilike("name", envName).single();
|
|
928
949
|
if (envError || !environments) {
|
|
@@ -938,7 +959,8 @@ var pullCommand = new Command4("pull").description("Pull environment variables f
|
|
|
938
959
|
p_environment_id: environmentId,
|
|
939
960
|
p_sync_secrets: includeSecrets,
|
|
940
961
|
p_sync_variables: true,
|
|
941
|
-
p_include_fallbacks: shouldFallback || false
|
|
962
|
+
p_include_fallbacks: shouldFallback || false,
|
|
963
|
+
...serviceId && { p_service_id: serviceId }
|
|
942
964
|
};
|
|
943
965
|
const { data: variables, error: varError } = await client.rpc("get_variables_for_sync", rpcParams);
|
|
944
966
|
if (varError) {
|
|
@@ -957,7 +979,11 @@ File ${outputFile} already exists.`));
|
|
|
957
979
|
console.log(chalk4.gray("Use --force to overwrite."));
|
|
958
980
|
process.exit(1);
|
|
959
981
|
}
|
|
960
|
-
const
|
|
982
|
+
const filterTags = options.tag || config?.tags || [];
|
|
983
|
+
const vars = filterTags.length > 0 ? variables.filter((v) => {
|
|
984
|
+
const t = v.tags || [];
|
|
985
|
+
return t.length === 0 || t.some((tag) => filterTags.includes(tag));
|
|
986
|
+
}).sort((a, b) => a.key.localeCompare(b.key)) : variables.sort((a, b) => a.key.localeCompare(b.key));
|
|
961
987
|
let resolvedMap = null;
|
|
962
988
|
if (shouldResolve) {
|
|
963
989
|
spinner.text = "Resolving variable references...";
|
|
@@ -1034,7 +1060,9 @@ File ${outputFile} already exists.`));
|
|
|
1034
1060
|
writeFileSync2(outputFile, content + "\n");
|
|
1035
1061
|
const secretCount = vars.filter((v) => v.is_secret).length;
|
|
1036
1062
|
const plainCount = vars.length - secretCount;
|
|
1037
|
-
|
|
1063
|
+
const serviceInfo = serviceName ? ` (service: ${serviceName})` : "";
|
|
1064
|
+
const tagInfo = filterTags.length > 0 ? ` (tags: ${filterTags.join(", ")})` : "";
|
|
1065
|
+
spinner.succeed(`Pulled ${vars.length} variables to ${outputFile} (${format})${serviceInfo}${tagInfo}`);
|
|
1038
1066
|
console.log(chalk4.gray(` ${plainCount} plain, ${secretCount} secrets`));
|
|
1039
1067
|
client.rpc("log_variable_access", {
|
|
1040
1068
|
p_environment_id: environmentId,
|
|
@@ -1191,7 +1219,7 @@ function validateVariableName(name, config) {
|
|
|
1191
1219
|
}
|
|
1192
1220
|
|
|
1193
1221
|
// src/commands/push.ts
|
|
1194
|
-
var pushCommand = new Command5("push").description("Push local .env file to EnvManager").option("--org <name>", "Organization name (required if you belong to multiple)").option("-e, --environment <name>", 'Environment name (default: from config or "development")').option("-p, --project <id>", "Project ID (default: from config)").option("-i, --input <file>", "Input file path (default: .env)").option("--secrets <keys>", "Comma-separated list of keys to mark as secrets").option("--all-secrets", "Mark all variables as secrets").option("--dry-run", "Show what would be pushed without making changes").action(async (options) => {
|
|
1222
|
+
var pushCommand = new Command5("push").description("Push local .env file to EnvManager").option("--org <name>", "Organization name (required if you belong to multiple)").option("-e, --environment <name>", 'Environment name (default: from config or "development")').option("-p, --project <id>", "Project ID (default: from config)").option("-i, --input <file>", "Input file path (default: .env)").option("--secrets <keys>", "Comma-separated list of keys to mark as secrets").option("--all-secrets", "Mark all variables as secrets").option("--service <name>", "Push to specific service").option("--dry-run", "Show what would be pushed without making changes").action(async (options) => {
|
|
1195
1223
|
const spinner = ora4("Reading .env file...").start();
|
|
1196
1224
|
try {
|
|
1197
1225
|
const config = loadConfig();
|
|
@@ -1242,6 +1270,16 @@ var pushCommand = new Command5("push").description("Push local .env file to EnvM
|
|
|
1242
1270
|
spinner.fail(error instanceof Error ? error.message : "Failed to resolve project");
|
|
1243
1271
|
process.exit(1);
|
|
1244
1272
|
}
|
|
1273
|
+
const serviceName = options.service || config?.service;
|
|
1274
|
+
let serviceId;
|
|
1275
|
+
if (serviceName) {
|
|
1276
|
+
try {
|
|
1277
|
+
serviceId = await resolveServiceId(serviceName, client, projectId);
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
spinner.fail(error instanceof Error ? error.message : "Failed to resolve service");
|
|
1280
|
+
process.exit(1);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1245
1283
|
spinner.text = "Fetching environment...";
|
|
1246
1284
|
const { data: environment, error: envError } = await client.from("environments").select("id, name, project_id").eq("project_id", projectId).ilike("name", envName).single();
|
|
1247
1285
|
if (envError || !environment) {
|
|
@@ -1297,12 +1335,24 @@ var pushCommand = new Command5("push").description("Push local .env file to EnvM
|
|
|
1297
1335
|
key: v.key,
|
|
1298
1336
|
value: v.value
|
|
1299
1337
|
}));
|
|
1300
|
-
|
|
1338
|
+
let existingQuery = client.from("variables").select("key").eq("environment_id", environment.id);
|
|
1339
|
+
if (serviceId) {
|
|
1340
|
+
existingQuery = existingQuery.eq("service_id", serviceId);
|
|
1341
|
+
} else {
|
|
1342
|
+
existingQuery = existingQuery.is("service_id", null);
|
|
1343
|
+
}
|
|
1344
|
+
const { data: existingVars } = await existingQuery;
|
|
1301
1345
|
const existingKeys = new Set((existingVars || []).map((v) => v.key));
|
|
1302
1346
|
const keysToUpdate = variablesData.filter((v) => existingKeys.has(v.key));
|
|
1303
1347
|
const keysToInsert = variablesData.filter((v) => !existingKeys.has(v.key));
|
|
1304
1348
|
if (keysToUpdate.length > 0) {
|
|
1305
|
-
|
|
1349
|
+
let deleteQuery = client.from("variables").delete().eq("environment_id", environment.id).in("key", keysToUpdate.map((v) => v.key));
|
|
1350
|
+
if (serviceId) {
|
|
1351
|
+
deleteQuery = deleteQuery.eq("service_id", serviceId);
|
|
1352
|
+
} else {
|
|
1353
|
+
deleteQuery = deleteQuery.is("service_id", null);
|
|
1354
|
+
}
|
|
1355
|
+
const { error: deleteError } = await deleteQuery;
|
|
1306
1356
|
if (deleteError) {
|
|
1307
1357
|
spinner.fail("Failed to update existing variables");
|
|
1308
1358
|
console.error(chalk6.red(deleteError.message));
|
|
@@ -1313,14 +1363,16 @@ var pushCommand = new Command5("push").description("Push local .env file to EnvM
|
|
|
1313
1363
|
variables_data: variablesData,
|
|
1314
1364
|
environment_id_param: environment.id,
|
|
1315
1365
|
organization_id_param: organizationId,
|
|
1316
|
-
import_as_secrets: markAsSecrets
|
|
1366
|
+
import_as_secrets: markAsSecrets,
|
|
1367
|
+
...serviceId && { service_id_param: serviceId }
|
|
1317
1368
|
});
|
|
1318
1369
|
if (pushError) {
|
|
1319
1370
|
spinner.fail("Push failed");
|
|
1320
1371
|
console.error(chalk6.red(pushError.message));
|
|
1321
1372
|
process.exit(1);
|
|
1322
1373
|
}
|
|
1323
|
-
|
|
1374
|
+
const serviceInfo = serviceName ? ` (service: ${serviceName})` : "";
|
|
1375
|
+
spinner.succeed(`Pushed ${vars.length} variables to ${envName}${serviceInfo}`);
|
|
1324
1376
|
if (keysToUpdate.length > 0) {
|
|
1325
1377
|
console.log(chalk6.gray(` ${keysToInsert.length} inserted, ${keysToUpdate.length} updated`));
|
|
1326
1378
|
}
|
|
@@ -1340,7 +1392,7 @@ import chalk7 from "chalk";
|
|
|
1340
1392
|
import ora5 from "ora";
|
|
1341
1393
|
import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
|
|
1342
1394
|
import { resolve as resolve3 } from "path";
|
|
1343
|
-
var diffCommand = new Command6("diff").description("Show differences between local .env and EnvManager").option("--org <name>", "Organization name (required if you belong to multiple)").option("-e, --environment <name>", 'Environment name (default: from config or "development")').option("-p, --project <id>", "Project ID (default: from config)").option("-i, --input <file>", "Input file path (default: .env)").option("--keys-only", "Only show key names, not values").action(async (options) => {
|
|
1395
|
+
var diffCommand = new Command6("diff").description("Show differences between local .env and EnvManager").option("--org <name>", "Organization name (required if you belong to multiple)").option("-e, --environment <name>", 'Environment name (default: from config or "development")').option("-p, --project <id>", "Project ID (default: from config)").option("-i, --input <file>", "Input file path (default: .env)").option("--keys-only", "Only show key names, not values").option("--tag <tags...>", "Filter by tags (untagged variables always included)").action(async (options) => {
|
|
1344
1396
|
const spinner = ora5("Comparing...").start();
|
|
1345
1397
|
try {
|
|
1346
1398
|
const config = loadConfig();
|
|
@@ -1392,8 +1444,13 @@ var diffCommand = new Command6("diff").description("Show differences between loc
|
|
|
1392
1444
|
console.error(chalk7.red(varError.message));
|
|
1393
1445
|
process.exit(1);
|
|
1394
1446
|
}
|
|
1447
|
+
const filterTags = options.tag || config?.tags || [];
|
|
1448
|
+
const filteredRemoteData = filterTags.length > 0 ? (remoteVarsData || []).filter((v) => {
|
|
1449
|
+
const t = v.tags || [];
|
|
1450
|
+
return t.length === 0 || t.some((tag) => filterTags.includes(tag));
|
|
1451
|
+
}) : remoteVarsData || [];
|
|
1395
1452
|
const remoteVars = /* @__PURE__ */ new Map();
|
|
1396
|
-
for (const v of
|
|
1453
|
+
for (const v of filteredRemoteData) {
|
|
1397
1454
|
remoteVars.set(v.key, v);
|
|
1398
1455
|
}
|
|
1399
1456
|
spinner.stop();
|
|
@@ -1728,14 +1785,16 @@ async function subscribeToVariableChanges(environmentId, onEvent, onStatus) {
|
|
|
1728
1785
|
});
|
|
1729
1786
|
return subscription;
|
|
1730
1787
|
}
|
|
1731
|
-
async function fetchAllVariables(environmentId, includeSecrets = true) {
|
|
1788
|
+
async function fetchAllVariables(environmentId, includeSecrets = true, serviceId) {
|
|
1732
1789
|
const client = await createClient();
|
|
1733
|
-
const
|
|
1790
|
+
const rpcParams = {
|
|
1734
1791
|
p_environment_id: environmentId,
|
|
1735
1792
|
p_sync_secrets: includeSecrets,
|
|
1736
1793
|
p_sync_variables: true,
|
|
1737
|
-
p_include_fallbacks: false
|
|
1738
|
-
|
|
1794
|
+
p_include_fallbacks: false,
|
|
1795
|
+
...serviceId && { p_service_id: serviceId }
|
|
1796
|
+
};
|
|
1797
|
+
const { data: variables, error } = await client.rpc("get_variables_for_sync", rpcParams);
|
|
1739
1798
|
if (error) {
|
|
1740
1799
|
throw new Error(`Failed to fetch variables: ${error.message}`);
|
|
1741
1800
|
}
|
|
@@ -1877,7 +1936,7 @@ function mergeWithRemote(local, remoteVariables, strategy) {
|
|
|
1877
1936
|
}
|
|
1878
1937
|
|
|
1879
1938
|
// src/commands/dev.ts
|
|
1880
|
-
var devCommand = new Command9("dev").description("Start real-time sync daemon - watches for remote variable changes").option("--org <name>", "Organization name (required if you belong to multiple)").option("-e, --environment <name>", 'Environment name (default: from config or "development")').option("-p, --project <id>", "Project ID (default: from config)").option("--output <file>", "Output file path (default: .env)").option("--no-watch", "Disable local file watching").option("--strategy <type>", "Merge strategy: remote_wins, local_wins, merge_new (default: remote_wins)", "remote_wins").action(async (options) => {
|
|
1939
|
+
var devCommand = new Command9("dev").description("Start real-time sync daemon - watches for remote variable changes").option("--org <name>", "Organization name (required if you belong to multiple)").option("-e, --environment <name>", 'Environment name (default: from config or "development")').option("-p, --project <id>", "Project ID (default: from config)").option("--output <file>", "Output file path (default: .env)").option("--no-watch", "Disable local file watching").option("--strategy <type>", "Merge strategy: remote_wins, local_wins, merge_new (default: remote_wins)", "remote_wins").option("--tag <tags...>", "Filter by tags (untagged variables always included)").option("--service <name>", "Filter by service name").action(async (options) => {
|
|
1881
1940
|
const spinner = ora7("Starting dev mode...").start();
|
|
1882
1941
|
try {
|
|
1883
1942
|
const config = loadConfig();
|
|
@@ -1907,6 +1966,16 @@ var devCommand = new Command9("dev").description("Start real-time sync daemon -
|
|
|
1907
1966
|
spinner.fail(error instanceof Error ? error.message : "Failed to resolve project");
|
|
1908
1967
|
process.exit(1);
|
|
1909
1968
|
}
|
|
1969
|
+
const serviceName = options.service || config?.service;
|
|
1970
|
+
let serviceId;
|
|
1971
|
+
if (serviceName) {
|
|
1972
|
+
try {
|
|
1973
|
+
serviceId = await resolveServiceId(serviceName, client, projectId);
|
|
1974
|
+
} catch (error) {
|
|
1975
|
+
spinner.fail(error instanceof Error ? error.message : "Failed to resolve service");
|
|
1976
|
+
process.exit(1);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1910
1979
|
spinner.text = "Fetching environment...";
|
|
1911
1980
|
const { data: environment, error: envError } = await client.from("environments").select("id, name, project_id").eq("project_id", projectId).ilike("name", envName).single();
|
|
1912
1981
|
if (envError || !environment) {
|
|
@@ -1914,8 +1983,16 @@ var devCommand = new Command9("dev").description("Start real-time sync daemon -
|
|
|
1914
1983
|
process.exit(1);
|
|
1915
1984
|
}
|
|
1916
1985
|
const environmentId = environment.id;
|
|
1986
|
+
const filterTags = options.tag || config?.tags || [];
|
|
1987
|
+
const applyTagFilter = (vars) => {
|
|
1988
|
+
if (filterTags.length === 0) return vars;
|
|
1989
|
+
return vars.filter((v) => {
|
|
1990
|
+
const t = v.tags || [];
|
|
1991
|
+
return t.length === 0 || t.some((tag) => filterTags.includes(tag));
|
|
1992
|
+
});
|
|
1993
|
+
};
|
|
1917
1994
|
spinner.text = "Performing initial sync...";
|
|
1918
|
-
const remoteVariables = await fetchAllVariables(environmentId, true);
|
|
1995
|
+
const remoteVariables = applyTagFilter(await fetchAllVariables(environmentId, true, serviceId));
|
|
1919
1996
|
let localVariables = /* @__PURE__ */ new Map();
|
|
1920
1997
|
if (existsSync7(outputFile)) {
|
|
1921
1998
|
const content = readFileSync6(outputFile, "utf-8");
|
|
@@ -1932,7 +2009,7 @@ var devCommand = new Command9("dev").description("Start real-time sync daemon -
|
|
|
1932
2009
|
let isPaused = false;
|
|
1933
2010
|
let lastRemoteKeys = null;
|
|
1934
2011
|
async function syncRemoteToLocal(silent = false) {
|
|
1935
|
-
const updatedVariables = await fetchAllVariables(environmentId, true);
|
|
2012
|
+
const updatedVariables = applyTagFilter(await fetchAllVariables(environmentId, true, serviceId));
|
|
1936
2013
|
const currentLocal = /* @__PURE__ */ new Map();
|
|
1937
2014
|
if (existsSync7(outputFile)) {
|
|
1938
2015
|
const content = readFileSync6(outputFile, "utf-8");
|
|
@@ -2024,6 +2101,9 @@ Realtime error: ${message || ""}`));
|
|
|
2024
2101
|
console.log("");
|
|
2025
2102
|
console.log(chalk10.cyan(" Project:"), projectId);
|
|
2026
2103
|
console.log(chalk10.cyan(" Environment:"), envName);
|
|
2104
|
+
if (serviceName) {
|
|
2105
|
+
console.log(chalk10.cyan(" Service:"), serviceName);
|
|
2106
|
+
}
|
|
2027
2107
|
console.log(chalk10.cyan(" Output:"), outputFile);
|
|
2028
2108
|
console.log(chalk10.cyan(" Strategy:"), strategy);
|
|
2029
2109
|
console.log(chalk10.cyan(" Watching:"), watchLocal ? "local + remote" : "remote only");
|
|
@@ -2328,7 +2408,7 @@ function generateYamlTemplate(variables, options = {}) {
|
|
|
2328
2408
|
}
|
|
2329
2409
|
|
|
2330
2410
|
// src/commands/init.ts
|
|
2331
|
-
var initCommand = new Command10("init").description("Initialize project configuration and generate .env.template from EnvManager").option("--org <name>", "Organization name (required if you belong to multiple)").option("-e, --environment <name>", 'Environment name (default: "development")').option("-p, --project <id>", "Project ID").option("--format <type>", "Template format: simple or yaml (default: simple)", "simple").option("-f, --force", "Overwrite existing files without prompting").action(async (options) => {
|
|
2411
|
+
var initCommand = new Command10("init").description("Initialize project configuration and generate .env.template from EnvManager").option("--org <name>", "Organization name (required if you belong to multiple)").option("-e, --environment <name>", 'Environment name (default: "development")').option("-p, --project <id>", "Project ID").option("--format <type>", "Template format: simple or yaml (default: simple)", "simple").option("-f, --force", "Overwrite existing files without prompting").option("--service <name>", "Service name to scope variables to").action(async (options) => {
|
|
2332
2412
|
const spinner = ora8("Initializing project...").start();
|
|
2333
2413
|
try {
|
|
2334
2414
|
const configPath = resolve5("envmanager.json");
|
|
@@ -2389,13 +2469,39 @@ Environment "${envName}" not found. Available:`));
|
|
|
2389
2469
|
}
|
|
2390
2470
|
process.exit(1);
|
|
2391
2471
|
}
|
|
2472
|
+
let selectedServiceName;
|
|
2473
|
+
let serviceIdForSync;
|
|
2474
|
+
const { data: projectServices } = await client.from("services").select("id, name").eq("project_id", projectId).order("sort_order");
|
|
2475
|
+
if (options.service) {
|
|
2476
|
+
spinner.text = "Resolving service...";
|
|
2477
|
+
try {
|
|
2478
|
+
serviceIdForSync = await resolveServiceId(options.service, client, projectId);
|
|
2479
|
+
selectedServiceName = options.service;
|
|
2480
|
+
} catch (error) {
|
|
2481
|
+
spinner.fail(error instanceof Error ? error.message : "Failed to resolve service");
|
|
2482
|
+
process.exit(1);
|
|
2483
|
+
}
|
|
2484
|
+
} else if (projectServices && projectServices.length > 0) {
|
|
2485
|
+
spinner.stop();
|
|
2486
|
+
console.log("");
|
|
2487
|
+
console.log(chalk11.cyan("Services available in this project:"));
|
|
2488
|
+
projectServices.forEach((s) => {
|
|
2489
|
+
console.log(chalk11.gray(` - ${s.name}`));
|
|
2490
|
+
});
|
|
2491
|
+
console.log(chalk11.gray("\n Use --service <name> to scope variables to a specific service."));
|
|
2492
|
+
console.log(chalk11.gray(" Without --service, all variables will be synced."));
|
|
2493
|
+
console.log("");
|
|
2494
|
+
spinner.start("Fetching variables...");
|
|
2495
|
+
}
|
|
2392
2496
|
spinner.text = "Fetching variables...";
|
|
2393
|
-
const
|
|
2497
|
+
const rpcParams = {
|
|
2394
2498
|
p_environment_id: environment.id,
|
|
2395
2499
|
p_sync_secrets: false,
|
|
2396
2500
|
p_sync_variables: true,
|
|
2397
|
-
p_include_fallbacks: false
|
|
2398
|
-
|
|
2501
|
+
p_include_fallbacks: false,
|
|
2502
|
+
...serviceIdForSync && { p_service_id: serviceIdForSync }
|
|
2503
|
+
};
|
|
2504
|
+
const { data: variables, error: varError } = await client.rpc("get_variables_for_sync", rpcParams);
|
|
2399
2505
|
if (varError) {
|
|
2400
2506
|
spinner.fail("Failed to fetch variables");
|
|
2401
2507
|
console.error(chalk11.red(varError.message));
|
|
@@ -2419,6 +2525,9 @@ File ${templatePath} already exists. Use --force to overwrite.`));
|
|
|
2419
2525
|
project_id: projectId,
|
|
2420
2526
|
environment: environment.name
|
|
2421
2527
|
};
|
|
2528
|
+
if (selectedServiceName) {
|
|
2529
|
+
config.service = selectedServiceName;
|
|
2530
|
+
}
|
|
2422
2531
|
writeFileSync4(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
2423
2532
|
const templateVars = varsArray.map((v) => ({
|
|
2424
2533
|
key: v.key,
|
|
@@ -2432,7 +2541,8 @@ File ${templatePath} already exists. Use --force to overwrite.`));
|
|
|
2432
2541
|
const simpleContent = generateTemplate(templateVars, { includeDefaults: true });
|
|
2433
2542
|
writeFileSync4(templatePath, simpleContent);
|
|
2434
2543
|
}
|
|
2435
|
-
|
|
2544
|
+
const serviceInfo = selectedServiceName ? ` (service: ${selectedServiceName})` : "";
|
|
2545
|
+
spinner.succeed(`Project initialized${serviceInfo}`);
|
|
2436
2546
|
console.log("");
|
|
2437
2547
|
console.log(chalk11.green("Created:"));
|
|
2438
2548
|
console.log(chalk11.gray(` ${configPath}`));
|