create-asaje-go-vue 0.3.9 → 0.3.10

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
@@ -124,6 +124,7 @@ npx -p create-asaje-go-vue@latest asaje print-railway-config ./my-app
124
124
  npx -p create-asaje-go-vue@latest asaje print-railway-config ./my-app --environment production
125
125
  npx -p create-asaje-go-vue@latest asaje print-railway-config ./my-app --environment production --json
126
126
  npx -p create-asaje-go-vue@latest asaje print-railway-config ./my-app --environment production --show-secrets
127
+ npx -p create-asaje-go-vue@latest asaje print-railway-config ./my-app --write-config
127
128
  ```
128
129
 
129
130
  ### Export resolved Railway config snapshot
@@ -259,6 +260,8 @@ npx -p create-asaje-go-vue@latest asaje destroy-railway ./my-app --scope project
259
260
  - supports `--json` for machine-readable output
260
261
  - redacts secret-looking values in the printed output by default
261
262
  - supports `--show-secrets` when you explicitly want the raw values in the output
263
+ - supports `--write-config` to write the resolved per-service variables back into `asaje.config.json` for later adjustment
264
+ - when `--write-config` is used without `--show-secrets`, sensitive-looking variables are omitted from the file to avoid writing redacted placeholders
262
265
 
263
266
  ## What `asaje export-railway-config` does
264
267
 
@@ -43,6 +43,7 @@ import {
43
43
  buildCreateRailwayServices,
44
44
  buildSyncedRailwayManifest,
45
45
  findCreatedRailwayService,
46
+ findRailwayAppServiceSpec,
46
47
  findRailwayService,
47
48
  findRailwayServiceByKey,
48
49
  normalizeRailwayServiceName,
@@ -74,6 +75,7 @@ import {
74
75
  import { collectCreateAnswers } from "../src/create/questions.js";
75
76
  import { collectStartAnswers } from "../src/start/answers.js";
76
77
  import {
78
+ ENV_FILE_SPECS,
77
79
  ensureEnvFiles,
78
80
  installProjectDependencies,
79
81
  loadRuntimeConfig,
@@ -91,6 +93,7 @@ import {
91
93
  buildRailwayBrowserOrigins,
92
94
  buildRailwayVariableDiff,
93
95
  formatRailwayVariableValue,
96
+ isSensitiveRailwayVariableKey,
94
97
  sanitizeVariablesForOutput,
95
98
  } from "../src/railway/variables.js";
96
99
  import { resolveInvocation } from "../src/cli/invocation.js";
@@ -966,6 +969,13 @@ async function runPrintRailwayConfig(argv) {
966
969
  showSecrets: args.showSecrets,
967
970
  });
968
971
 
972
+ if (args.writeConfig) {
973
+ await writeResolvedRailwayConfigToProjectConfig(payload, {
974
+ quiet: args.json,
975
+ showSecrets: args.showSecrets,
976
+ });
977
+ }
978
+
969
979
  if (args.json) {
970
980
  console.log(JSON.stringify(payload, null, 2));
971
981
  return;
@@ -1238,6 +1248,7 @@ function parsePrintRailwayConfigArgs(argv) {
1238
1248
  environment: undefined,
1239
1249
  json: false,
1240
1250
  showSecrets: false,
1251
+ writeConfig: false,
1241
1252
  };
1242
1253
  const positionals = [];
1243
1254
 
@@ -1254,6 +1265,11 @@ function parsePrintRailwayConfigArgs(argv) {
1254
1265
  continue;
1255
1266
  }
1256
1267
 
1268
+ if (arg === "--write-config") {
1269
+ options.writeConfig = true;
1270
+ continue;
1271
+ }
1272
+
1257
1273
  if (arg === "--environment" || arg === "-e") {
1258
1274
  options.environment = argv[index + 1] || options.environment;
1259
1275
  index += 1;
@@ -1268,7 +1284,7 @@ function parsePrintRailwayConfigArgs(argv) {
1268
1284
  positionals.push(arg);
1269
1285
  }
1270
1286
 
1271
- options.directory = positionals[0] || options.directory;
1287
+ options.directory = resolveDirectoryArg(positionals, options.directory);
1272
1288
  return options;
1273
1289
  }
1274
1290
 
@@ -1314,7 +1330,7 @@ function parseExportRailwayConfigArgs(argv) {
1314
1330
  positionals.push(arg);
1315
1331
  }
1316
1332
 
1317
- options.directory = positionals[0] || options.directory;
1333
+ options.directory = resolveDirectoryArg(positionals, options.directory);
1318
1334
  return options;
1319
1335
  }
1320
1336
 
@@ -1372,7 +1388,7 @@ function parseImportRailwayConfigArgs(argv) {
1372
1388
  positionals.push(arg);
1373
1389
  }
1374
1390
 
1375
- options.directory = positionals[0] || options.directory;
1391
+ options.directory = resolveDirectoryArg(positionals, options.directory);
1376
1392
  if (!options.file) {
1377
1393
  throw new Error("import-railway-config requires --file <snapshot.json>.");
1378
1394
  }
@@ -1403,7 +1419,7 @@ function parseSyncProjectConfigArgs(argv) {
1403
1419
  positionals.push(arg);
1404
1420
  }
1405
1421
 
1406
- options.directory = positionals[0] || options.directory;
1422
+ options.directory = resolveDirectoryArg(positionals, options.directory);
1407
1423
  return options;
1408
1424
  }
1409
1425
 
@@ -1425,7 +1441,7 @@ function parseSyncGithubWorkflowArgs(argv) {
1425
1441
  positionals.push(arg);
1426
1442
  }
1427
1443
 
1428
- options.directory = positionals[0] || options.directory;
1444
+ options.directory = resolveDirectoryArg(positionals, options.directory);
1429
1445
  return options;
1430
1446
  }
1431
1447
 
@@ -1447,7 +1463,7 @@ function parseSyncReadmeArgs(argv) {
1447
1463
  positionals.push(arg);
1448
1464
  }
1449
1465
 
1450
- options.directory = positionals[0] || options.directory;
1466
+ options.directory = resolveDirectoryArg(positionals, options.directory);
1451
1467
  return options;
1452
1468
  }
1453
1469
 
@@ -1523,7 +1539,7 @@ function parseDiffRailwayConfigArgs(argv) {
1523
1539
  positionals.push(arg);
1524
1540
  }
1525
1541
 
1526
- options.directory = positionals[0] || options.directory;
1542
+ options.directory = resolveDirectoryArg(positionals, options.directory);
1527
1543
  if (!options.compareEnvironment && !options.compareFile) {
1528
1544
  throw new Error("diff-railway-config requires --compare-environment <name> or --compare-file <snapshot.json>.");
1529
1545
  }
@@ -2285,18 +2301,29 @@ async function resolveRailwayVariablePlan(config) {
2285
2301
  realtime: findRailwayServiceByKey(config.services, config.appServiceSpecs, config.manifest, "realtime"),
2286
2302
  worker: findRailwayServiceByKey(config.services, config.appServiceSpecs, config.manifest, "worker"),
2287
2303
  };
2304
+ const managedAppServices = config.includeUnresolvedServices
2305
+ ? {
2306
+ admin: resolveManagedServiceReference(config.services, config.appServiceSpecs, config.manifest, "admin"),
2307
+ api: resolveManagedServiceReference(config.services, config.appServiceSpecs, config.manifest, "api"),
2308
+ landing: resolveManagedServiceReference(config.services, config.appServiceSpecs, config.manifest, "landing"),
2309
+ pwa: resolveManagedServiceReference(config.services, config.appServiceSpecs, config.manifest, "pwa"),
2310
+ realtime: resolveManagedServiceReference(config.services, config.appServiceSpecs, config.manifest, "realtime"),
2311
+ worker: resolveManagedServiceReference(config.services, config.appServiceSpecs, config.manifest, "worker"),
2312
+ }
2313
+ : appServices;
2288
2314
 
2289
2315
  const serviceRegistry = {
2290
- admin: appServices.admin ? { name: appServices.admin.name, variables: {} } : null,
2291
- api: appServices.api ? { name: appServices.api.name, variables: {} } : null,
2292
- landing: appServices.landing ? { name: appServices.landing.name, variables: {} } : null,
2316
+ admin: managedAppServices.admin ? { name: managedAppServices.admin.name, variables: {} } : null,
2317
+ api: managedAppServices.api ? { name: managedAppServices.api.name, variables: {} } : null,
2318
+ landing: managedAppServices.landing ? { name: managedAppServices.landing.name, variables: {} } : null,
2293
2319
  objectStorage: infra.objectStorage ? { name: infra.objectStorage.name, variables: {} } : null,
2294
2320
  postgres: infra.postgres ? { name: infra.postgres.name, variables: {} } : null,
2295
- pwa: appServices.pwa ? { name: appServices.pwa.name, variables: {} } : null,
2321
+ pwa: managedAppServices.pwa ? { name: managedAppServices.pwa.name, variables: {} } : null,
2296
2322
  rabbitmq: infra.rabbitmq ? { name: infra.rabbitmq.name, variables: {} } : null,
2297
- realtime: appServices.realtime ? { name: appServices.realtime.name, variables: {} } : null,
2298
- worker: appServices.worker ? { name: appServices.worker.name, variables: {} } : null,
2323
+ realtime: managedAppServices.realtime ? { name: managedAppServices.realtime.name, variables: {} } : null,
2324
+ worker: managedAppServices.worker ? { name: managedAppServices.worker.name, variables: {} } : null,
2299
2325
  };
2326
+ const browserOrigins = buildManagedBrowserOrigins(config.services, config.appServiceSpecs, config.manifest);
2300
2327
 
2301
2328
  for (const spec of config.appServiceSpecs) {
2302
2329
  const service = findRailwayServiceByKey(config.services, config.appServiceSpecs, config.manifest, spec.key);
@@ -2321,22 +2348,22 @@ async function resolveRailwayVariablePlan(config) {
2321
2348
  }
2322
2349
 
2323
2350
  const notices = [];
2324
- if (!appServices.api) {
2351
+ if (!managedAppServices.api) {
2325
2352
  notices.push("api service not found, skipping API variable wiring");
2326
2353
  }
2327
- if (!appServices.realtime) {
2354
+ if (!managedAppServices.realtime) {
2328
2355
  notices.push("realtime-gateway service not found, skipping realtime variable wiring");
2329
2356
  }
2330
- if (!appServices.admin) {
2357
+ if (!managedAppServices.admin) {
2331
2358
  notices.push("admin service not found, skipping admin variable wiring");
2332
2359
  }
2333
- if (!appServices.worker) {
2360
+ if (!managedAppServices.worker) {
2334
2361
  notices.push("worker service not found, skipping worker variable wiring");
2335
2362
  }
2336
- if (!appServices.landing) {
2363
+ if (!managedAppServices.landing) {
2337
2364
  notices.push("landing service not found, skipping landing variable wiring");
2338
2365
  }
2339
- if (!appServices.pwa) {
2366
+ if (!managedAppServices.pwa) {
2340
2367
  notices.push("pwa service not found, skipping pwa variable wiring");
2341
2368
  }
2342
2369
  if (!infra.postgres) {
@@ -2349,11 +2376,11 @@ async function resolveRailwayVariablePlan(config) {
2349
2376
  notices.push("object-storage resource not found, S3 variable wiring will be skipped");
2350
2377
  }
2351
2378
 
2352
- if (variablesMode !== "replace" && appServices.api) {
2379
+ if (variablesMode !== "replace" && managedAppServices.api) {
2353
2380
  const existingApiVariables = await loadRailwayServiceVariables(
2354
2381
  config.projectDir,
2355
2382
  config.railwayContext.environmentRef,
2356
- appServices.api.name,
2383
+ managedAppServices.api.name,
2357
2384
  );
2358
2385
  const variables = {};
2359
2386
  const sharedSecrets = buildRailwaySharedSecrets(localEnv, existingApiVariables);
@@ -2367,45 +2394,43 @@ async function resolveRailwayVariablePlan(config) {
2367
2394
  if (infra.objectStorage?.name) {
2368
2395
  Object.assign(variables, buildObjectStorageVariables(infra.objectStorage.name));
2369
2396
  }
2370
- const browserOrigins = buildRailwayBrowserOrigins(appServices);
2371
2397
  if (browserOrigins) {
2372
2398
  variables.CORS_ALLOWED_ORIGINS = browserOrigins;
2373
2399
  }
2374
- variables.PUBLIC_API_BASE_URL = `https://${railwayReference(appServices.api.name, "RAILWAY_PUBLIC_DOMAIN")}`;
2400
+ variables.PUBLIC_API_BASE_URL = `https://${railwayReference(managedAppServices.api.name, "RAILWAY_PUBLIC_DOMAIN")}`;
2375
2401
  assignManagedRailwayServiceVariables(serviceRegistry.api, variables, variablesMode);
2376
2402
  }
2377
2403
 
2378
- if (variablesMode !== "replace" && appServices.realtime) {
2404
+ if (variablesMode !== "replace" && managedAppServices.realtime) {
2379
2405
  const variables = {};
2380
2406
  Object.assign(variables, buildRealtimeDefaults(localEnv));
2381
2407
  if (infra.rabbitmq?.name) {
2382
2408
  variables.RABBITMQ_URL = buildRabbitMqUrlReference(infra.rabbitmq.name);
2383
2409
  }
2384
- if (appServices.api?.name) {
2385
- variables.JWT_SECRET = railwayReference(appServices.api.name, "JWT_SECRET");
2410
+ if (managedAppServices.api?.name) {
2411
+ variables.JWT_SECRET = railwayReference(managedAppServices.api.name, "JWT_SECRET");
2386
2412
  }
2387
- const browserOrigins = buildRailwayBrowserOrigins(appServices);
2388
2413
  if (browserOrigins) {
2389
2414
  variables.CORS_ALLOWED_ORIGINS = browserOrigins;
2390
2415
  }
2391
2416
  assignManagedRailwayServiceVariables(serviceRegistry.realtime, variables, variablesMode);
2392
2417
  }
2393
2418
 
2394
- if (variablesMode !== "replace" && appServices.admin) {
2419
+ if (variablesMode !== "replace" && managedAppServices.admin) {
2395
2420
  const variables = {};
2396
2421
  Object.assign(variables, buildAdminDefaults(localEnv));
2397
- if (appServices.api?.name) {
2398
- variables.VITE_API_BASE_URL = `https://${railwayReference(appServices.api.name, "RAILWAY_PUBLIC_DOMAIN")}/api/v1`;
2422
+ if (managedAppServices.api?.name) {
2423
+ variables.VITE_API_BASE_URL = `https://${railwayReference(managedAppServices.api.name, "RAILWAY_PUBLIC_DOMAIN")}/api/v1`;
2399
2424
  }
2400
- if (appServices.realtime?.name) {
2401
- variables.VITE_REALTIME_BASE_URL = `https://${railwayReference(appServices.realtime.name, "RAILWAY_PUBLIC_DOMAIN")}`;
2425
+ if (managedAppServices.realtime?.name) {
2426
+ variables.VITE_REALTIME_BASE_URL = `https://${railwayReference(managedAppServices.realtime.name, "RAILWAY_PUBLIC_DOMAIN")}`;
2402
2427
  }
2403
2428
  assignManagedRailwayServiceVariables(serviceRegistry.admin, variables, variablesMode);
2404
2429
  }
2405
2430
 
2406
- if (variablesMode !== "replace" && appServices.worker) {
2407
- const existingApiVariables = appServices.api?.name
2408
- ? await loadRailwayServiceVariables(config.projectDir, config.railwayContext.environmentRef, appServices.api.name)
2431
+ if (variablesMode !== "replace" && managedAppServices.worker) {
2432
+ const existingApiVariables = managedAppServices.api?.name
2433
+ ? await loadRailwayServiceVariables(config.projectDir, config.railwayContext.environmentRef, managedAppServices.api.name)
2409
2434
  : {};
2410
2435
  const variables = {};
2411
2436
  const sharedSecrets = buildRailwaySharedSecrets(localEnv, existingApiVariables);
@@ -2423,26 +2448,26 @@ async function resolveRailwayVariablePlan(config) {
2423
2448
  assignManagedRailwayServiceVariables(serviceRegistry.worker, variables, variablesMode);
2424
2449
  }
2425
2450
 
2426
- if (variablesMode !== "replace" && appServices.landing) {
2451
+ if (variablesMode !== "replace" && managedAppServices.landing) {
2427
2452
  const variables = {};
2428
2453
  Object.assign(variables, buildLandingDefaults(localEnv));
2429
- if (appServices.api?.name) {
2430
- variables.API_BASE_URL = `https://${railwayReference(appServices.api.name, "RAILWAY_PUBLIC_DOMAIN")}`;
2454
+ if (managedAppServices.api?.name) {
2455
+ variables.API_BASE_URL = `https://${railwayReference(managedAppServices.api.name, "RAILWAY_PUBLIC_DOMAIN")}`;
2431
2456
  }
2432
- if (appServices.pwa?.name) {
2433
- variables.PWA_BASE_URL = `https://${railwayReference(appServices.pwa.name, "RAILWAY_PUBLIC_DOMAIN")}`;
2457
+ if (managedAppServices.pwa?.name) {
2458
+ variables.PWA_BASE_URL = `https://${railwayReference(managedAppServices.pwa.name, "RAILWAY_PUBLIC_DOMAIN")}`;
2434
2459
  }
2435
2460
  assignManagedRailwayServiceVariables(serviceRegistry.landing, variables, variablesMode);
2436
2461
  }
2437
2462
 
2438
- if (variablesMode !== "replace" && appServices.pwa) {
2463
+ if (variablesMode !== "replace" && managedAppServices.pwa) {
2439
2464
  const variables = {};
2440
2465
  Object.assign(variables, buildPwaDefaults(localEnv));
2441
- if (appServices.api?.name) {
2442
- variables.VITE_API_BASE_URL = `https://${railwayReference(appServices.api.name, "RAILWAY_PUBLIC_DOMAIN")}/api/v1`;
2466
+ if (managedAppServices.api?.name) {
2467
+ variables.VITE_API_BASE_URL = `https://${railwayReference(managedAppServices.api.name, "RAILWAY_PUBLIC_DOMAIN")}/api/v1`;
2443
2468
  }
2444
- if (appServices.realtime?.name) {
2445
- variables.VITE_REALTIME_BASE_URL = `https://${railwayReference(appServices.realtime.name, "RAILWAY_PUBLIC_DOMAIN")}`;
2469
+ if (managedAppServices.realtime?.name) {
2470
+ variables.VITE_REALTIME_BASE_URL = `https://${railwayReference(managedAppServices.realtime.name, "RAILWAY_PUBLIC_DOMAIN")}`;
2446
2471
  }
2447
2472
  assignManagedRailwayServiceVariables(serviceRegistry.pwa, variables, variablesMode);
2448
2473
  }
@@ -2471,7 +2496,7 @@ async function resolveRailwayVariablePlan(config) {
2471
2496
  }
2472
2497
 
2473
2498
  return {
2474
- appServices,
2499
+ appServices: managedAppServices,
2475
2500
  infra,
2476
2501
  notices,
2477
2502
  serviceRegistry,
@@ -2728,6 +2753,87 @@ function printResolvedRailwayConfig(payload) {
2728
2753
  }
2729
2754
  }
2730
2755
 
2756
+ function resolveDirectoryArg(positionals, fallback) {
2757
+ return positionals.at(-1) || fallback;
2758
+ }
2759
+
2760
+ function buildManagedBrowserOrigins(services, appServiceSpecs, manifest) {
2761
+ return buildRailwayBrowserOrigins({
2762
+ admin: resolveManagedServiceReference(services, appServiceSpecs, manifest, "admin"),
2763
+ landing: resolveManagedServiceReference(services, appServiceSpecs, manifest, "landing"),
2764
+ pwa: resolveManagedServiceReference(services, appServiceSpecs, manifest, "pwa"),
2765
+ });
2766
+ }
2767
+
2768
+ function resolveManagedServiceReference(services, appServiceSpecs, manifest, key) {
2769
+ const discoveredService = findRailwayServiceByKey(services, appServiceSpecs, manifest, key);
2770
+ if (discoveredService?.name) {
2771
+ return discoveredService;
2772
+ }
2773
+
2774
+ const spec = findRailwayAppServiceSpec(appServiceSpecs, key);
2775
+ if (!spec) {
2776
+ return null;
2777
+ }
2778
+
2779
+ const serviceName = manifest.appServices?.[key]?.serviceName || resolveRailwayServiceName(spec, manifest.projectSlug);
2780
+ return serviceName ? { name: serviceName } : null;
2781
+ }
2782
+
2783
+ async function writeResolvedRailwayConfigToProjectConfig(payload, options = {}) {
2784
+ const projectConfig = (await loadProjectConfig(payload.directory)) || {};
2785
+ const nextProjectConfig = {
2786
+ ...projectConfig,
2787
+ railway: {
2788
+ ...(projectConfig.railway || {}),
2789
+ },
2790
+ };
2791
+ const variablesByService = buildProjectConfigRailwayVariables(payload.services, options.showSecrets);
2792
+
2793
+ if (payload.environment.configKey) {
2794
+ const previousEnvironment = nextProjectConfig.railway.environments?.[payload.environment.configKey] || {};
2795
+ nextProjectConfig.railway.environments = {
2796
+ ...(nextProjectConfig.railway.environments || {}),
2797
+ [payload.environment.configKey]: {
2798
+ ...previousEnvironment,
2799
+ ...(payload.environment.railwayEnvironment && !previousEnvironment.railwayEnvironment
2800
+ ? { railwayEnvironment: payload.environment.railwayEnvironment }
2801
+ : {}),
2802
+ variables: {
2803
+ ...(previousEnvironment.variables || {}),
2804
+ services: variablesByService,
2805
+ },
2806
+ },
2807
+ };
2808
+ } else {
2809
+ nextProjectConfig.railway.variables = {
2810
+ ...(nextProjectConfig.railway.variables || {}),
2811
+ services: variablesByService,
2812
+ };
2813
+ }
2814
+
2815
+ await writeProjectConfigFile(payload.directory, nextProjectConfig);
2816
+ if (options.quiet) {
2817
+ return;
2818
+ }
2819
+
2820
+ console.log(`- Wrote resolved Railway variables to ${pc.bold(path.join(payload.directory, "asaje.config.json"))}`);
2821
+ if (!options.showSecrets) {
2822
+ console.log(`- ${pc.yellow("Sensitive variables were omitted; rerun with --show-secrets to include them")}`);
2823
+ }
2824
+ }
2825
+
2826
+ function buildProjectConfigRailwayVariables(services, showSecrets) {
2827
+ return Object.fromEntries(
2828
+ services.map((service) => [
2829
+ service.key,
2830
+ Object.fromEntries(
2831
+ Object.entries(service.variables || {}).filter(([key]) => showSecrets || !isSensitiveRailwayVariableKey(key)),
2832
+ ),
2833
+ ]),
2834
+ );
2835
+ }
2836
+
2731
2837
  function printRailwayConfigSnapshotDiff(diff) {
2732
2838
  console.log(pc.bold("\nRailway config diff"));
2733
2839
  console.log(`- Left: ${pc.bold(formatRailwayDiffSide(diff.left))}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-asaje-go-vue",
3
- "version": "0.3.9",
3
+ "version": "0.3.10",
4
4
  "description": "CLI to scaffold, configure, and run the Asaje Go + Vue boilerplate",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/args.js CHANGED
@@ -126,13 +126,13 @@ export function parseUpdateArgs(argv) {
126
126
  positionals.push(arg);
127
127
  }
128
128
 
129
- options.directory = positionals[0] || options.directory;
129
+ options.directory = resolveDirectoryArg(positionals, options.directory);
130
130
  options.include = uniquePaths(options.include);
131
131
  return options;
132
132
  }
133
133
 
134
134
  export function parseDirectoryArgs(argv) {
135
- return { directory: argv[0] || "." };
135
+ return { directory: resolveDirectoryArg(argv, ".") };
136
136
  }
137
137
 
138
138
  export function parseSetupRailwayArgs(argv, defaults = {}) {
@@ -215,7 +215,7 @@ export function parseSetupRailwayArgs(argv, defaults = {}) {
215
215
  positionals.push(arg);
216
216
  }
217
217
 
218
- options.directory = positionals[0] || options.directory;
218
+ options.directory = resolveDirectoryArg(positionals, options.directory);
219
219
  options.services = uniqueStrings(options.services);
220
220
  return options;
221
221
  }
@@ -279,7 +279,7 @@ export function parseDeployRailwayArgs(argv) {
279
279
  positionals.push(arg);
280
280
  }
281
281
 
282
- options.directory = positionals[0] || options.directory;
282
+ options.directory = resolveDirectoryArg(positionals, options.directory);
283
283
  options.services = uniqueStrings(options.services);
284
284
  return options;
285
285
  }
@@ -344,7 +344,7 @@ export function parseDestroyRailwayArgs(argv) {
344
344
  positionals.push(arg);
345
345
  }
346
346
 
347
- options.directory = positionals[0] || options.directory;
347
+ options.directory = resolveDirectoryArg(positionals, options.directory);
348
348
  if (!["environment", "project"].includes(options.scope)) {
349
349
  throw new Error("--scope must be either 'environment' or 'project'");
350
350
  }
@@ -434,10 +434,14 @@ export function parseStartArgs(argv) {
434
434
  positionals.push(arg);
435
435
  }
436
436
 
437
- options.directory = positionals[0] || options.directory;
437
+ options.directory = resolveDirectoryArg(positionals, options.directory);
438
438
  return options;
439
439
  }
440
440
 
441
+ function resolveDirectoryArg(positionals, fallback) {
442
+ return positionals.at(-1) || fallback;
443
+ }
444
+
441
445
  function splitCsv(value) {
442
446
  return value
443
447
  .split(",")
@@ -84,14 +84,18 @@ export function formatRailwayVariableValue(key, value, showSecrets = false) {
84
84
  return "<unset>";
85
85
  }
86
86
 
87
- const normalizedKey = String(key || "").toUpperCase();
88
- if (!showSecrets && /(SECRET|PASSWORD|TOKEN|API_KEY|ACCESS_KEY|SECRET_KEY|ERLANG_COOKIE)/.test(normalizedKey)) {
87
+ if (!showSecrets && isSensitiveRailwayVariableKey(key)) {
89
88
  return redactRailwayVariableValue(value);
90
89
  }
91
90
 
92
91
  return String(value);
93
92
  }
94
93
 
94
+ export function isSensitiveRailwayVariableKey(key) {
95
+ const normalizedKey = String(key || "").toUpperCase();
96
+ return /(SECRET|PASSWORD|TOKEN|API_KEY|ACCESS_KEY|SECRET_KEY|ERLANG_COOKIE)/.test(normalizedKey);
97
+ }
98
+
95
99
  export function redactRailwayVariableValue(value) {
96
100
  const textValue = String(value || "");
97
101
  if (textValue.length <= 8) {