create-asaje-go-vue 0.2.2 → 0.2.4

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
@@ -95,7 +95,9 @@ npx -p create-asaje-go-vue@latest asaje sync-railway-env ./my-app --dry-run
95
95
  - checks that the Railway CLI is installed and authenticated
96
96
  - reads the linked Railway project context
97
97
  - provisions PostgreSQL, RabbitMQ, and S3-compatible object storage on Railway
98
- - wires Railway variables for `api`, `realtime-gateway`, and `admin` when those services already exist in the Railway project
98
+ - creates missing Railway app services for `api`, `realtime-gateway`, and `admin`
99
+ - wires Railway variables for `api`, `realtime-gateway`, and `admin`
100
+ - triggers the first Railway deployment for each app service using the service-local `Dockerfile` and `railway.json`
99
101
  - generates missing app secrets such as `JWT_SECRET` and `SWAGGER_PASSWORD`, while reusing existing Railway values when present
100
102
  - supports `--dry-run` to preview provisioning and variable changes without applying them
101
103
  - writes an `asaje.railway.json` manifest in the target project for future runs, including discovered Railway app service names
@@ -24,6 +24,28 @@ const EXCLUDED_TEMPLATE_PATHS = ["cli"];
24
24
  const RAILWAY_GRAPHQL_ENDPOINT = "https://backboard.railway.com/graphql/v2";
25
25
  const RAILWAY_MANIFEST_FILENAME = "asaje.railway.json";
26
26
  const DEFAULT_RAILWAY_BUCKET = "boilerplate-files";
27
+ const RAILWAY_SERVICE_DISCOVERY_RETRY_DELAY_MS = 2000;
28
+ const RAILWAY_SERVICE_DISCOVERY_RETRY_COUNT = 5;
29
+ const RAILWAY_APP_SERVICE_SPECS = [
30
+ {
31
+ aliases: ["api", "backend", "server"],
32
+ directory: "api",
33
+ key: "api",
34
+ serviceName: "api",
35
+ },
36
+ {
37
+ aliases: ["admin", "frontend", "web"],
38
+ directory: "admin",
39
+ key: "admin",
40
+ serviceName: "admin",
41
+ },
42
+ {
43
+ aliases: ["realtime-gateway", "realtime"],
44
+ directory: "realtime-gateway",
45
+ key: "realtime",
46
+ serviceName: "realtime-gateway",
47
+ },
48
+ ];
27
49
  const ENV_FILE_SPECS = [
28
50
  { envPath: "admin/.env", examplePath: "admin/.env.example" },
29
51
  { envPath: "api/.env", examplePath: "api/.env.example" },
@@ -897,13 +919,16 @@ async function runSetupRailway(argv) {
897
919
  await ensureProjectStructure(projectDir);
898
920
  await ensureRailwayCliInstalled();
899
921
  await ensureRailwayAuthenticated(projectDir, answers.environment);
922
+ await ensureRailwayEnvironmentLinked(projectDir, answers.environment);
900
923
 
901
924
  const manifest = await readRailwayManifest(projectDir);
902
925
  manifest.resources ||= {};
903
926
  const railwayContext = await loadRailwayContext(projectDir, answers.environment);
904
927
  railwayContext.environmentRef = answers.environment || railwayContext.environmentId || railwayContext.environmentName;
905
928
  const existingServices = await discoverRailwayServices(railwayContext, projectDir);
906
- const summary = [];
929
+ const resourceSummary = [];
930
+ const appServiceSummary = [];
931
+ const deploySummary = [];
907
932
  const variableSummary = [];
908
933
 
909
934
  console.log(pc.bold("\nProvisioning"));
@@ -918,11 +943,11 @@ async function runSetupRailway(argv) {
918
943
  projectDir,
919
944
  railwayContext,
920
945
  });
921
- summary.push(postgresResult);
946
+ resourceSummary.push(postgresResult);
922
947
 
923
948
  const rabbitMqResult = await ensureRailwayResource({
924
949
  aliases: ["rabbitmq"],
925
- commandArgs: ["deploy", "--template", "rabbitmq"],
950
+ commandArgs: ["deploy", "--template", "RabbitMQ"],
926
951
  dryRun: answers.dryRun,
927
952
  existingServices,
928
953
  key: "rabbitmq",
@@ -930,7 +955,7 @@ async function runSetupRailway(argv) {
930
955
  projectDir,
931
956
  railwayContext,
932
957
  });
933
- summary.push(rabbitMqResult);
958
+ resourceSummary.push(rabbitMqResult);
934
959
 
935
960
  const objectStorageResult = await ensureRailwayResource({
936
961
  aliases: ["object-storage", "storage", "simple-s3", "minio"],
@@ -949,10 +974,25 @@ async function runSetupRailway(argv) {
949
974
  projectDir,
950
975
  railwayContext,
951
976
  });
952
- summary.push(objectStorageResult);
977
+ resourceSummary.push(objectStorageResult);
953
978
 
954
- manifest.bucket = answers.bucket;
979
+ console.log(pc.bold("\nApplication services"));
955
980
  manifest.appServices ||= {};
981
+ for (const spec of RAILWAY_APP_SERVICE_SPECS) {
982
+ const serviceResult = await ensureRailwayAppService({
983
+ aliases: spec.aliases,
984
+ dryRun: answers.dryRun,
985
+ existingServices,
986
+ key: spec.key,
987
+ manifest,
988
+ projectDir,
989
+ railwayContext,
990
+ serviceName: spec.serviceName,
991
+ });
992
+ appServiceSummary.push(serviceResult);
993
+ }
994
+
995
+ manifest.bucket = answers.bucket;
956
996
  manifest.environmentId = railwayContext.environmentId || manifest.environmentId || null;
957
997
  manifest.environmentName = railwayContext.environmentName || manifest.environmentName || null;
958
998
  manifest.projectId = railwayContext.projectId || manifest.projectId || null;
@@ -969,11 +1009,28 @@ async function runSetupRailway(argv) {
969
1009
  services: servicesAfterProvision,
970
1010
  summary: variableSummary,
971
1011
  });
1012
+ const deploymentResults = await deployRailwayAppServices({
1013
+ dryRun: answers.dryRun,
1014
+ manifest,
1015
+ projectDir,
1016
+ railwayContext,
1017
+ services: servicesAfterProvision,
1018
+ });
1019
+ deploySummary.push(...deploymentResults);
972
1020
 
973
1021
  if (!answers.dryRun) {
974
1022
  await writeRailwayManifest(projectDir, manifest);
975
1023
  }
976
- printRailwaySetupSummary(projectDir, railwayContext, summary, variableSummary, answers.bucket, answers.dryRun);
1024
+ printRailwaySetupSummary({
1025
+ appServiceSummary,
1026
+ bucket: answers.bucket,
1027
+ deploySummary,
1028
+ dryRun: answers.dryRun,
1029
+ projectDir,
1030
+ railwayContext,
1031
+ resourceSummary,
1032
+ variableSummary,
1033
+ });
977
1034
  }
978
1035
 
979
1036
  async function runSyncRailwayEnv(argv) {
@@ -984,6 +1041,7 @@ async function runSyncRailwayEnv(argv) {
984
1041
  await ensureProjectStructure(projectDir);
985
1042
  await ensureRailwayCliInstalled();
986
1043
  await ensureRailwayAuthenticated(projectDir, answers.environment);
1044
+ await ensureRailwayEnvironmentLinked(projectDir, answers.environment);
987
1045
 
988
1046
  const manifest = await readRailwayManifest(projectDir);
989
1047
  manifest.resources ||= {};
@@ -1015,14 +1073,16 @@ async function runSyncRailwayEnv(argv) {
1015
1073
  await writeRailwayManifest(projectDir, manifest);
1016
1074
  }
1017
1075
 
1018
- printRailwaySetupSummary(
1076
+ printRailwaySetupSummary({
1077
+ appServiceSummary: [],
1078
+ bucket: manifest.bucket || answers.bucket,
1079
+ deploySummary: [],
1080
+ dryRun: answers.dryRun,
1019
1081
  projectDir,
1020
1082
  railwayContext,
1021
- [],
1083
+ resourceSummary: [],
1022
1084
  variableSummary,
1023
- manifest.bucket || answers.bucket,
1024
- answers.dryRun,
1025
- );
1085
+ });
1026
1086
  }
1027
1087
 
1028
1088
  function parseDirectoryArgs(argv) {
@@ -1154,6 +1214,21 @@ async function ensureRailwayAuthenticated(projectDir, environment) {
1154
1214
  }
1155
1215
  }
1156
1216
 
1217
+ async function ensureRailwayEnvironmentLinked(projectDir, environment) {
1218
+ if (!environment) {
1219
+ return;
1220
+ }
1221
+
1222
+ const result = await execa("railway", ["environment", "link", environment], {
1223
+ cwd: projectDir,
1224
+ reject: false,
1225
+ });
1226
+
1227
+ if (result.exitCode !== 0) {
1228
+ throw new Error(`Unable to link Railway environment ${environment}. Make sure it exists and try again.`);
1229
+ }
1230
+ }
1231
+
1157
1232
  async function readRailwayManifest(projectDir) {
1158
1233
  const manifestPath = path.join(projectDir, RAILWAY_MANIFEST_FILENAME);
1159
1234
  if (!(await fs.pathExists(manifestPath))) {
@@ -1344,35 +1419,136 @@ async function ensureRailwayResource(config) {
1344
1419
  }
1345
1420
 
1346
1421
  if (manifestEntry?.status === "created" || manifestEntry?.status === "existing") {
1347
- console.log(`- ${pc.cyan(config.key)} already tracked in ${RAILWAY_MANIFEST_FILENAME}`);
1348
- return {
1349
- key: config.key,
1350
- serviceName: manifestEntry.serviceName || null,
1351
- status: "tracked",
1352
- };
1422
+ console.log(`- ${pc.yellow(config.key)} tracked in ${RAILWAY_MANIFEST_FILENAME} but not found remotely, recreating...`);
1353
1423
  }
1354
1424
 
1355
1425
  console.log(`- creating ${pc.cyan(config.key)}...`);
1426
+ const servicesBefore = normalizeRailwayServices(config.existingServices);
1356
1427
  if (!config.dryRun) {
1357
1428
  await runRailwayCommand(config.projectDir, config.railwayContext.environmentRef, config.commandArgs);
1358
1429
  }
1359
1430
 
1431
+ let createdService = null;
1432
+ if (!config.dryRun) {
1433
+ createdService = await waitForCreatedRailwayService({
1434
+ aliases: config.aliases,
1435
+ beforeServices: servicesBefore,
1436
+ key: config.key,
1437
+ manifestEntry,
1438
+ projectDir: config.projectDir,
1439
+ railwayContext: config.railwayContext,
1440
+ });
1441
+ }
1442
+
1360
1443
  config.manifest.resources[config.key] = {
1361
1444
  bucket: config.metadata?.bucket || null,
1362
1445
  detectedAt: new Date().toISOString(),
1363
- serviceId: null,
1364
- serviceName: null,
1446
+ serviceId: createdService?.id || null,
1447
+ serviceName: createdService?.name || null,
1365
1448
  source: "cli",
1366
1449
  status: "created",
1367
1450
  };
1368
1451
 
1369
1452
  return {
1370
1453
  key: config.key,
1371
- serviceName: null,
1454
+ serviceName: createdService?.name || null,
1372
1455
  status: config.dryRun ? "dry-run" : "created",
1373
1456
  };
1374
1457
  }
1375
1458
 
1459
+ async function ensureRailwayAppService(config) {
1460
+ const manifestEntry = config.manifest.appServices?.[config.key];
1461
+ const existingService = findRailwayService(
1462
+ config.existingServices,
1463
+ config.aliases,
1464
+ manifestEntry?.serviceName || config.serviceName,
1465
+ );
1466
+
1467
+ if (existingService) {
1468
+ config.manifest.appServices[config.key] = {
1469
+ serviceId: existingService.id || manifestEntry?.serviceId || null,
1470
+ serviceName: existingService.name || manifestEntry?.serviceName || config.serviceName,
1471
+ };
1472
+
1473
+ console.log(`- ${pc.cyan(config.serviceName)} already present${existingService.name ? ` (${existingService.name})` : ""}`);
1474
+ return {
1475
+ key: config.serviceName,
1476
+ serviceName: existingService.name || config.serviceName,
1477
+ status: "existing",
1478
+ };
1479
+ }
1480
+
1481
+ if (manifestEntry?.serviceName) {
1482
+ console.log(`- ${pc.yellow(config.serviceName)} tracked in ${RAILWAY_MANIFEST_FILENAME} but not found remotely, recreating...`);
1483
+ }
1484
+
1485
+ console.log(`- creating ${pc.cyan(config.serviceName)} service...`);
1486
+ const servicesBefore = normalizeRailwayServices(config.existingServices);
1487
+ if (!config.dryRun) {
1488
+ await runRailwayCommand(config.projectDir, config.railwayContext.environmentRef, ["add", "--service", config.serviceName]);
1489
+ }
1490
+
1491
+ let createdService = null;
1492
+ if (!config.dryRun) {
1493
+ createdService = await waitForCreatedRailwayService({
1494
+ aliases: config.aliases,
1495
+ beforeServices: servicesBefore,
1496
+ key: config.serviceName,
1497
+ manifestEntry,
1498
+ projectDir: config.projectDir,
1499
+ railwayContext: config.railwayContext,
1500
+ });
1501
+ }
1502
+
1503
+ config.manifest.appServices[config.key] = {
1504
+ serviceId: createdService?.id || null,
1505
+ serviceName: createdService?.name || config.serviceName,
1506
+ };
1507
+
1508
+ return {
1509
+ key: config.serviceName,
1510
+ serviceName: createdService?.name || config.serviceName,
1511
+ status: config.dryRun ? "dry-run" : "created",
1512
+ };
1513
+ }
1514
+
1515
+ async function deployRailwayAppServices(config) {
1516
+ console.log(pc.bold("\nDeployments"));
1517
+
1518
+ const summary = [];
1519
+ for (const spec of RAILWAY_APP_SERVICE_SPECS) {
1520
+ const manifestEntry = config.manifest.appServices?.[spec.key];
1521
+ const service = findRailwayService(config.services, spec.aliases, manifestEntry?.serviceName || spec.serviceName);
1522
+ const targetServiceName = service?.name || manifestEntry?.serviceName || spec.serviceName;
1523
+
1524
+ if (!service && !config.dryRun) {
1525
+ console.log(`- ${pc.yellow(spec.serviceName)} service not found, skipping deployment`);
1526
+ continue;
1527
+ }
1528
+
1529
+ if (config.dryRun) {
1530
+ console.log(`- would deploy ${pc.cyan(targetServiceName)} from ${spec.directory}/`);
1531
+ summary.push({ directory: spec.directory, serviceName: targetServiceName, status: "dry-run" });
1532
+ continue;
1533
+ }
1534
+
1535
+ console.log(`- deploying ${pc.cyan(targetServiceName)} from ${spec.directory}/...`);
1536
+ await runRailwayCommand(config.projectDir, config.railwayContext.environmentRef, [
1537
+ "up",
1538
+ spec.directory,
1539
+ "--service",
1540
+ targetServiceName,
1541
+ "--path-as-root",
1542
+ "--detach",
1543
+ "--message",
1544
+ `asaje setup-railway: deploy ${targetServiceName}`,
1545
+ ]);
1546
+ summary.push({ directory: spec.directory, serviceName: targetServiceName, status: "deployed" });
1547
+ }
1548
+
1549
+ return summary;
1550
+ }
1551
+
1376
1552
  async function wireRailwayVariables(config) {
1377
1553
  console.log(pc.bold("\nVariables"));
1378
1554
 
@@ -1416,6 +1592,15 @@ async function wireRailwayVariables(config) {
1416
1592
  if (!appServices.admin) {
1417
1593
  console.log(`- ${pc.yellow("admin")} service not found, skipping admin variable wiring`);
1418
1594
  }
1595
+ if (!infra.postgres) {
1596
+ console.log(`- ${pc.yellow("postgres")} resource not found, DATABASE_URL wiring will be skipped`);
1597
+ }
1598
+ if (!infra.rabbitmq) {
1599
+ console.log(`- ${pc.yellow("rabbitmq")} resource not found, RABBITMQ_URL wiring will be skipped`);
1600
+ }
1601
+ if (!infra.objectStorage) {
1602
+ console.log(`- ${pc.yellow("object-storage")} resource not found, S3 variable wiring will be skipped`);
1603
+ }
1419
1604
 
1420
1605
  if (appServices.api) {
1421
1606
  const existingApiVariables = await loadRailwayServiceVariables(
@@ -1648,6 +1833,30 @@ async function loadRailwayServiceVariables(projectDir, environment, serviceName)
1648
1833
  }
1649
1834
  }
1650
1835
 
1836
+ async function waitForCreatedRailwayService(config) {
1837
+ for (let attempt = 0; attempt < RAILWAY_SERVICE_DISCOVERY_RETRY_COUNT; attempt += 1) {
1838
+ const servicesAfter = await discoverRailwayServices(config.railwayContext, config.projectDir);
1839
+ const createdService = findCreatedRailwayService({
1840
+ aliases: config.aliases,
1841
+ beforeServices: config.beforeServices,
1842
+ manifestServiceName: config.manifestEntry?.serviceName,
1843
+ servicesAfter,
1844
+ });
1845
+
1846
+ if (createdService) {
1847
+ return createdService;
1848
+ }
1849
+
1850
+ if (attempt < RAILWAY_SERVICE_DISCOVERY_RETRY_COUNT - 1) {
1851
+ await sleep(RAILWAY_SERVICE_DISCOVERY_RETRY_DELAY_MS);
1852
+ }
1853
+ }
1854
+
1855
+ throw new Error(
1856
+ `Railway command for ${config.key} finished but the new service was not detected afterwards. Check the Railway dashboard/logs, then rerun \`asaje setup-railway\` or \`asaje sync-railway-env\` once the service appears.`,
1857
+ );
1858
+ }
1859
+
1651
1860
  function findRailwayService(services, aliases, preferredName) {
1652
1861
  if (preferredName) {
1653
1862
  const exact = services.find(
@@ -1696,6 +1905,32 @@ function normalizeRailwayServices(services) {
1696
1905
  return normalized;
1697
1906
  }
1698
1907
 
1908
+ function findCreatedRailwayService(config) {
1909
+ const beforeServices = normalizeRailwayServices(config.beforeServices);
1910
+ const afterServices = normalizeRailwayServices(config.servicesAfter);
1911
+ const beforeKeys = new Set(beforeServices.map(createRailwayServiceIdentity));
1912
+ const newServices = afterServices.filter((service) => !beforeKeys.has(createRailwayServiceIdentity(service)));
1913
+
1914
+ if (newServices.length === 1) {
1915
+ return newServices[0];
1916
+ }
1917
+
1918
+ const aliasMatch = findRailwayService(newServices, config.aliases, config.manifestServiceName);
1919
+ if (aliasMatch) {
1920
+ return aliasMatch;
1921
+ }
1922
+
1923
+ return null;
1924
+ }
1925
+
1926
+ function createRailwayServiceIdentity(service) {
1927
+ if (service?.id) {
1928
+ return `id:${service.id}`;
1929
+ }
1930
+
1931
+ return `name:${normalizeRailwayServiceName(service?.name)}`;
1932
+ }
1933
+
1699
1934
  function normalizeRailwayVariables(input) {
1700
1935
  const normalized = {};
1701
1936
 
@@ -1778,6 +2013,12 @@ function visitRailwayJson(input, visitor) {
1778
2013
  }
1779
2014
  }
1780
2015
 
2016
+ function sleep(delayMs) {
2017
+ return new Promise((resolve) => {
2018
+ setTimeout(resolve, delayMs);
2019
+ });
2020
+ }
2021
+
1781
2022
  async function runRailwayCommand(projectDir, environment, args) {
1782
2023
  const result = await execa("railway", buildRailwayArgs(args, environment), {
1783
2024
  cwd: projectDir,
@@ -1796,6 +2037,17 @@ function buildRailwayArgs(args, environment) {
1796
2037
  return args;
1797
2038
  }
1798
2039
 
2040
+ const commandKey = args.slice(0, 2).join(" ");
2041
+ const supportsEnvironmentFlag =
2042
+ commandKey === "service status" ||
2043
+ commandKey === "variable list" ||
2044
+ commandKey === "variable set" ||
2045
+ args[0] === "up";
2046
+
2047
+ if (!supportsEnvironmentFlag) {
2048
+ return args;
2049
+ }
2050
+
1799
2051
  return [...args, "--environment", environment];
1800
2052
  }
1801
2053
 
@@ -1840,33 +2092,56 @@ function findFirstNestedValue(input, key) {
1840
2092
  return null;
1841
2093
  }
1842
2094
 
1843
- function printRailwaySetupSummary(projectDir, railwayContext, summary, variableSummary, bucket, dryRun) {
2095
+ function printRailwaySetupSummary(config) {
1844
2096
  console.log(pc.bold("\nRailway"));
1845
- console.log(`- Directory: ${pc.bold(projectDir)}`);
1846
- if (railwayContext.projectName || railwayContext.projectId) {
1847
- console.log(`- Project: ${pc.bold(railwayContext.projectName || railwayContext.projectId)}`);
2097
+ console.log(`- Directory: ${pc.bold(config.projectDir)}`);
2098
+ if (config.railwayContext.projectName || config.railwayContext.projectId) {
2099
+ console.log(`- Project: ${pc.bold(config.railwayContext.projectName || config.railwayContext.projectId)}`);
1848
2100
  }
1849
- if (railwayContext.environmentName || railwayContext.environmentId) {
1850
- console.log(`- Environment: ${pc.bold(railwayContext.environmentName || railwayContext.environmentId)}`);
2101
+ if (config.railwayContext.environmentName || config.railwayContext.environmentId) {
2102
+ console.log(`- Environment: ${pc.bold(config.railwayContext.environmentName || config.railwayContext.environmentId)}`);
1851
2103
  }
1852
- console.log(`- Bucket: ${pc.bold(bucket)}`);
2104
+ console.log(`- Bucket: ${pc.bold(config.bucket)}`);
1853
2105
 
1854
2106
  console.log(pc.bold("\nResources"));
1855
- for (const item of summary) {
1856
- const detail = item.serviceName ? ` (${item.serviceName})` : "";
1857
- console.log(`- ${pc.bold(item.key)}: ${item.status}${detail}`);
2107
+ if (config.resourceSummary.length === 0) {
2108
+ console.log("- No infrastructure resources were changed");
2109
+ } else {
2110
+ for (const item of config.resourceSummary) {
2111
+ const detail = item.serviceName ? ` (${item.serviceName})` : "";
2112
+ console.log(`- ${pc.bold(item.key)}: ${item.status}${detail}`);
2113
+ }
2114
+ }
2115
+
2116
+ console.log(pc.bold("\nApplication services"));
2117
+ if (config.appServiceSummary.length === 0) {
2118
+ console.log("- No application services were changed");
2119
+ } else {
2120
+ for (const item of config.appServiceSummary) {
2121
+ const detail = item.serviceName ? ` (${item.serviceName})` : "";
2122
+ console.log(`- ${pc.bold(item.key)}: ${item.status}${detail}`);
2123
+ }
2124
+ }
2125
+
2126
+ console.log(pc.bold("\nDeployments"));
2127
+ if (config.deploySummary.length === 0) {
2128
+ console.log("- No application deployments were triggered");
2129
+ } else {
2130
+ for (const item of config.deploySummary) {
2131
+ console.log(`- ${pc.bold(item.serviceName)}: ${item.status} from ${item.directory}/`);
2132
+ }
1858
2133
  }
1859
2134
 
1860
2135
  console.log(pc.bold("\nVariables"));
1861
- if (variableSummary.length === 0) {
2136
+ if (config.variableSummary.length === 0) {
1862
2137
  console.log("- No application variables were updated");
1863
2138
  } else {
1864
- for (const item of variableSummary) {
2139
+ for (const item of config.variableSummary) {
1865
2140
  console.log(`- ${pc.bold(item.serviceName)}: ${item.status} ${item.keys.join(", ")}`);
1866
2141
  }
1867
2142
  }
1868
2143
 
1869
- if (dryRun) {
2144
+ if (config.dryRun) {
1870
2145
  console.log(`- Dry run only, ${pc.bold(RAILWAY_MANIFEST_FILENAME)} was not written`);
1871
2146
  } else {
1872
2147
  console.log(`- Manifest written to ${pc.bold(RAILWAY_MANIFEST_FILENAME)} for future runs`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-asaje-go-vue",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "CLI to scaffold, configure, and run the Asaje Go + Vue boilerplate",
5
5
  "type": "module",
6
6
  "bin": {