create-asaje-go-vue 0.3.1 → 0.3.3

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
@@ -82,6 +82,15 @@ npx -p create-asaje-go-vue@latest asaje setup-railway ./my-app --dry-run
82
82
  npx -p create-asaje-go-vue@latest asaje update-railway ./my-app --yes
83
83
  ```
84
84
 
85
+ By default this manages four Railway app services:
86
+
87
+ - `api`
88
+ - `worker`
89
+ - `realtime-gateway`
90
+ - `admin`
91
+
92
+ The default `worker` service reuses `api/Dockerfile` and starts with `API_COMMAND=worker`.
93
+
85
94
  ### Sync Railway app variables
86
95
 
87
96
  ```bash
@@ -127,6 +136,7 @@ npx -p create-asaje-go-vue@latest asaje diff-railway-config ./my-app --file ./sn
127
136
 
128
137
  ```bash
129
138
  npx -p create-asaje-go-vue@latest asaje deploy-railway ./my-app
139
+ npx -p create-asaje-go-vue@latest asaje deploy-railway ./my-app --service worker
130
140
  npx -p create-asaje-go-vue@latest asaje deploy-railway ./my-app --service api
131
141
  npx -p create-asaje-go-vue@latest asaje deploy-railway ./my-app --services api,admin --dry-run
132
142
  ```
@@ -195,9 +205,9 @@ npx -p create-asaje-go-vue@latest asaje destroy-railway ./my-app --scope project
195
205
  - reads the linked Railway project context
196
206
  - provisions PostgreSQL, RabbitMQ, and S3-compatible object storage on Railway
197
207
  - creates missing Railway app services for the configured app service list
198
- - defaults to `api`, `realtime-gateway`, and `admin` when no custom Railway service config is present
208
+ - defaults to `api`, `worker`, `realtime-gateway`, and `admin` when no custom Railway service config is present
199
209
  - applies Railway variables from `asaje.config.json` when configured
200
- - keeps the legacy automatic variable wiring for `api`, `realtime-gateway`, and `admin` unless `railway.variablesMode` is set to `replace`
210
+ - keeps the legacy automatic variable wiring for `api`, `worker`, `realtime-gateway`, and `admin` unless `railway.variablesMode` is set to `replace`
201
211
  - triggers the first Railway deployment for each app service using the service-local `Dockerfile` and `railway.json`
202
212
  - generates missing app secrets such as `JWT_SECRET` and `SWAGGER_PASSWORD`, while reusing existing Railway values when present
203
213
  - supports `--dry-run` to preview provisioning and variable changes without applying them
@@ -218,7 +228,7 @@ npx -p create-asaje-go-vue@latest asaje destroy-railway ./my-app --scope project
218
228
  - reads the linked Railway project context
219
229
  - discovers existing Railway app and infra services
220
230
  - syncs configured Railway variables without provisioning infra resources
221
- - keeps the legacy automatic variable wiring for `api`, `realtime-gateway`, and `admin` unless `railway.variablesMode` is set to `replace`
231
+ - keeps the legacy automatic variable wiring for `api`, `worker`, `realtime-gateway`, and `admin` unless `railway.variablesMode` is set to `replace`
222
232
  - supports `--diff` to show what would be added or changed compared with the current Railway variables
223
233
  - supports `--dry-run` to preview variable changes without applying them
224
234
 
@@ -263,7 +273,7 @@ npx -p create-asaje-go-vue@latest asaje destroy-railway ./my-app --scope project
263
273
  - reads the linked Railway project context
264
274
  - discovers the existing Railway app services from the linked project or `asaje.railway.json`
265
275
  - triggers fresh Railway builds/deployments for the configured app service list from the current local source tree
266
- - defaults to `api`, `realtime-gateway`, and `admin` when no custom Railway service config is present
276
+ - defaults to `api`, `worker`, `realtime-gateway`, and `admin` when no custom Railway service config is present
267
277
  - supports `--service` and `--services` to redeploy only selected app services
268
278
  - supports `--dry-run` to preview which services would be redeployed
269
279
 
@@ -285,6 +295,13 @@ If the `railway` block is omitted, the CLI keeps the default built-in services a
285
295
  "directory": "api",
286
296
  "dockerfile": "api/Dockerfile"
287
297
  },
298
+ {
299
+ "key": "worker",
300
+ "directory": "api",
301
+ "baseName": "worker",
302
+ "aliases": ["worker", "api-worker"],
303
+ "dockerfile": "api/Dockerfile"
304
+ },
288
305
  {
289
306
  "key": "admin",
290
307
  "directory": "admin",
@@ -297,12 +314,6 @@ If the `railway` block is omitted, the CLI keeps the default built-in services a
297
314
  "aliases": ["realtime-gateway"],
298
315
  "dockerfile": "realtime-gateway/Dockerfile"
299
316
  },
300
- {
301
- "key": "worker",
302
- "directory": "worker-api",
303
- "baseName": "worker",
304
- "dockerfile": "worker-api/Dockerfile"
305
- },
306
317
  {
307
318
  "key": "marketing",
308
319
  "directory": "marketing",
@@ -403,7 +414,8 @@ Notes:
403
414
  - the service directory should contain the `Dockerfile` Railway will build from
404
415
  - custom services are provisioned and deployed by `setup-railway` and `deploy-railway`
405
416
  - `sync-railway-env` can now apply declarative variables to any managed service key, including custom services
406
- - with `variablesMode: "merge"`, the core services `api`, `realtime-gateway`, and `admin` still receive the legacy generated defaults unless you override them
417
+ - with `variablesMode: "merge"`, the core services `api`, `worker`, `realtime-gateway`, and `admin` still receive the legacy generated defaults unless you override them
418
+ - the default `worker` service reuses the `api` image and starts with `API_COMMAND=worker`
407
419
  - with `variablesMode: "replace"`, only the variables declared in `asaje.config.json` are applied
408
420
  - after changing the Railway config, run `asaje update-railway ./my-app --yes` to reconcile the linked Railway project with the new configuration
409
421
  - you can target a custom service with `asaje deploy-railway ./my-app --service worker`
@@ -34,6 +34,12 @@ const DEFAULT_RAILWAY_APP_SERVICE_SPECS = [
34
34
  directory: "api",
35
35
  key: "api",
36
36
  },
37
+ {
38
+ aliases: ["worker", "api-worker"],
39
+ baseName: "worker",
40
+ directory: "api",
41
+ key: "worker",
42
+ },
37
43
  {
38
44
  aliases: ["admin", "frontend", "web"],
39
45
  baseName: "admin",
@@ -1258,10 +1264,11 @@ async function runSetupRailway(argv) {
1258
1264
  const projectConfig = await loadProjectConfig(projectDir);
1259
1265
  const projectSlug = resolveProjectSlug(projectDir, projectConfig);
1260
1266
  const appServiceSpecs = resolveRailwayAppServiceSpecs(projectConfig);
1267
+ const selectedSpecs = resolveDeployRailwaySpecs(answers.services, appServiceSpecs);
1261
1268
  const requestedRailwayEnvironment = resolveRequestedRailwayEnvironmentRef(projectConfig, answers.environment);
1262
1269
 
1263
1270
  await ensureProjectStructure(projectDir);
1264
- await ensureRailwayAppServiceTargets(projectDir, appServiceSpecs);
1271
+ await ensureRailwayAppServiceTargets(projectDir, selectedSpecs);
1265
1272
  await ensureRailwayCliInstalled();
1266
1273
  await ensureRailwayAuthenticated(projectDir, requestedRailwayEnvironment);
1267
1274
  await ensureRailwayEnvironmentLinked(projectDir, requestedRailwayEnvironment);
@@ -1337,7 +1344,7 @@ async function runSetupRailway(argv) {
1337
1344
 
1338
1345
  console.log(pc.bold("\nApplication services"));
1339
1346
  manifest.appServices ||= {};
1340
- for (const spec of appServiceSpecs) {
1347
+ for (const spec of selectedSpecs) {
1341
1348
  const serviceName = resolveRailwayServiceName(spec, projectSlug);
1342
1349
  const serviceResult = await ensureRailwayAppService({
1343
1350
  aliases: spec.aliases,
@@ -1382,7 +1389,7 @@ async function runSetupRailway(argv) {
1382
1389
  projectDir,
1383
1390
  projectSlug,
1384
1391
  railwayContext,
1385
- selectedSpecs: appServiceSpecs,
1392
+ selectedSpecs,
1386
1393
  services: servicesAfterProvision,
1387
1394
  });
1388
1395
  deploySummary.push(...deploymentResults);
@@ -1752,10 +1759,12 @@ function parseDirectoryArgs(argv) {
1752
1759
  function parseSetupRailwayArgs(argv) {
1753
1760
  const options = {
1754
1761
  bucket: DEFAULT_RAILWAY_BUCKET,
1762
+ bucketProvided: false,
1755
1763
  directory: ".",
1756
1764
  diff: false,
1757
1765
  dryRun: false,
1758
1766
  environment: undefined,
1767
+ services: [],
1759
1768
  yes: false,
1760
1769
  };
1761
1770
  const positionals = [];
@@ -1780,12 +1789,14 @@ function parseSetupRailwayArgs(argv) {
1780
1789
 
1781
1790
  if (arg === "--bucket") {
1782
1791
  options.bucket = argv[index + 1] || options.bucket;
1792
+ options.bucketProvided = true;
1783
1793
  index += 1;
1784
1794
  continue;
1785
1795
  }
1786
1796
 
1787
1797
  if (arg.startsWith("--bucket=")) {
1788
1798
  options.bucket = arg.split("=")[1] || options.bucket;
1799
+ options.bucketProvided = true;
1789
1800
  continue;
1790
1801
  }
1791
1802
 
@@ -1800,10 +1811,33 @@ function parseSetupRailwayArgs(argv) {
1800
1811
  continue;
1801
1812
  }
1802
1813
 
1814
+ if (arg === "--service") {
1815
+ options.services.push(argv[index + 1] || "");
1816
+ index += 1;
1817
+ continue;
1818
+ }
1819
+
1820
+ if (arg.startsWith("--service=")) {
1821
+ options.services.push(arg.split("=")[1] || "");
1822
+ continue;
1823
+ }
1824
+
1825
+ if (arg === "--services") {
1826
+ options.services.push(...splitCsv(argv[index + 1] || ""));
1827
+ index += 1;
1828
+ continue;
1829
+ }
1830
+
1831
+ if (arg.startsWith("--services=")) {
1832
+ options.services.push(...splitCsv(arg.split("=")[1] || ""));
1833
+ continue;
1834
+ }
1835
+
1803
1836
  positionals.push(arg);
1804
1837
  }
1805
1838
 
1806
1839
  options.directory = positionals[0] || options.directory;
1840
+ options.services = [...new Set(options.services.map((service) => service.trim()).filter(Boolean))];
1807
1841
  return options;
1808
1842
  }
1809
1843
 
@@ -2194,19 +2228,23 @@ function parseDestroyRailwayArgs(argv) {
2194
2228
  }
2195
2229
 
2196
2230
  async function collectSetupRailwayAnswers(args) {
2231
+ const directory = args.directory;
2232
+ const bucketState = await resolveSetupRailwayBucketState(args, directory);
2233
+
2197
2234
  if (args.yes) {
2198
2235
  return {
2199
- bucket: args.bucket,
2200
- directory: args.directory,
2236
+ bucket: bucketState.bucket,
2237
+ directory,
2201
2238
  diff: args.diff,
2202
2239
  dryRun: args.dryRun,
2203
2240
  environment: args.environment,
2241
+ services: args.services,
2204
2242
  };
2205
2243
  }
2206
2244
 
2207
- const directory = await prompt(
2245
+ const selectedDirectory = await prompt(
2208
2246
  text({
2209
- defaultValue: args.directory,
2247
+ defaultValue: directory,
2210
2248
  message: "Project directory to configure on Railway?",
2211
2249
  placeholder: ".",
2212
2250
  validate(value) {
@@ -2215,18 +2253,23 @@ async function collectSetupRailwayAnswers(args) {
2215
2253
  }),
2216
2254
  );
2217
2255
 
2218
- const bucket = await prompt(
2219
- text({
2220
- defaultValue: args.bucket,
2221
- message: "Object storage bucket name?",
2222
- placeholder: DEFAULT_RAILWAY_BUCKET,
2223
- validate(value) {
2224
- return /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(value)
2225
- ? undefined
2226
- : "Use 3-63 lowercase letters, numbers, dots, or hyphens";
2227
- },
2228
- }),
2229
- );
2256
+ const selectedBucketState = await resolveSetupRailwayBucketState(args, selectedDirectory);
2257
+ let bucket = selectedBucketState.bucket;
2258
+
2259
+ if (!args.bucketProvided && !selectedBucketState.hasStoredBucket) {
2260
+ bucket = await prompt(
2261
+ text({
2262
+ defaultValue: selectedBucketState.bucket,
2263
+ message: "Object storage bucket name?",
2264
+ placeholder: DEFAULT_RAILWAY_BUCKET,
2265
+ validate(value) {
2266
+ return /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(value)
2267
+ ? undefined
2268
+ : "Use 3-63 lowercase letters, numbers, dots, or hyphens";
2269
+ },
2270
+ }),
2271
+ );
2272
+ }
2230
2273
 
2231
2274
  let environment = args.environment;
2232
2275
  if (!environment) {
@@ -2241,10 +2284,28 @@ async function collectSetupRailwayAnswers(args) {
2241
2284
 
2242
2285
  return {
2243
2286
  bucket,
2244
- directory,
2287
+ directory: selectedDirectory,
2245
2288
  diff: args.diff,
2246
2289
  dryRun: args.dryRun,
2247
2290
  environment: environment?.trim() || undefined,
2291
+ services: args.services,
2292
+ };
2293
+ }
2294
+
2295
+ async function resolveSetupRailwayBucketState(args, directory) {
2296
+ if (args.bucketProvided) {
2297
+ return {
2298
+ bucket: args.bucket,
2299
+ hasStoredBucket: true,
2300
+ };
2301
+ }
2302
+
2303
+ const projectDir = path.resolve(process.cwd(), directory || ".");
2304
+ const manifest = await readRailwayManifest(projectDir);
2305
+ const existingBucket = typeof manifest.bucket === "string" ? manifest.bucket.trim() : "";
2306
+ return {
2307
+ bucket: existingBucket || DEFAULT_RAILWAY_BUCKET,
2308
+ hasStoredBucket: Boolean(existingBucket),
2248
2309
  };
2249
2310
  }
2250
2311
 
@@ -3070,6 +3131,7 @@ async function resolveRailwayVariablePlan(config) {
3070
3131
  admin: findRailwayServiceByKey(config.services, config.appServiceSpecs, config.manifest, "admin"),
3071
3132
  api: findRailwayServiceByKey(config.services, config.appServiceSpecs, config.manifest, "api"),
3072
3133
  realtime: findRailwayServiceByKey(config.services, config.appServiceSpecs, config.manifest, "realtime"),
3134
+ worker: findRailwayServiceByKey(config.services, config.appServiceSpecs, config.manifest, "worker"),
3073
3135
  };
3074
3136
 
3075
3137
  const serviceRegistry = {
@@ -3079,6 +3141,7 @@ async function resolveRailwayVariablePlan(config) {
3079
3141
  postgres: infra.postgres ? { name: infra.postgres.name, variables: {} } : null,
3080
3142
  rabbitmq: infra.rabbitmq ? { name: infra.rabbitmq.name, variables: {} } : null,
3081
3143
  realtime: appServices.realtime ? { name: appServices.realtime.name, variables: {} } : null,
3144
+ worker: appServices.worker ? { name: appServices.worker.name, variables: {} } : null,
3082
3145
  };
3083
3146
 
3084
3147
  for (const spec of config.appServiceSpecs) {
@@ -3109,6 +3172,9 @@ async function resolveRailwayVariablePlan(config) {
3109
3172
  if (!appServices.admin) {
3110
3173
  notices.push("admin service not found, skipping admin variable wiring");
3111
3174
  }
3175
+ if (!appServices.worker) {
3176
+ notices.push("worker service not found, skipping worker variable wiring");
3177
+ }
3112
3178
  if (!infra.postgres) {
3113
3179
  notices.push("postgres resource not found, DATABASE_URL wiring will be skipped");
3114
3180
  }
@@ -3170,6 +3236,26 @@ async function resolveRailwayVariablePlan(config) {
3170
3236
  mergeRailwayServiceVariables(serviceRegistry.admin, variables);
3171
3237
  }
3172
3238
 
3239
+ if (variablesMode !== "replace" && appServices.worker) {
3240
+ const existingApiVariables = appServices.api?.name
3241
+ ? await loadRailwayServiceVariables(config.projectDir, config.railwayContext.environmentRef, appServices.api.name)
3242
+ : {};
3243
+ const variables = {};
3244
+ const sharedSecrets = buildRailwaySharedSecrets(localEnv, existingApiVariables);
3245
+ Object.assign(variables, sharedSecrets.api);
3246
+ variables.API_COMMAND = "worker";
3247
+ if (infra.postgres?.name) {
3248
+ variables.DATABASE_URL = railwayReference(infra.postgres.name, "DATABASE_URL");
3249
+ }
3250
+ if (infra.rabbitmq?.name) {
3251
+ variables.RABBITMQ_URL = buildRabbitMqUrlReference(infra.rabbitmq.name);
3252
+ }
3253
+ if (infra.objectStorage?.name) {
3254
+ Object.assign(variables, buildObjectStorageVariables(infra.objectStorage.name));
3255
+ }
3256
+ mergeRailwayServiceVariables(serviceRegistry.worker, variables);
3257
+ }
3258
+
3173
3259
  if (declaredVariables.hasDeclaredVariables) {
3174
3260
  if (Object.keys(declaredVariables.sharedVariables).length > 0) {
3175
3261
  for (const entry of Object.values(serviceRegistry)) {
@@ -3909,6 +3995,7 @@ function printRailwaySetupSummary(config) {
3909
3995
  if (config.railwayContext.environmentName || config.railwayContext.environmentId) {
3910
3996
  console.log(`- Environment: ${pc.bold(config.railwayContext.environmentName || config.railwayContext.environmentId)}`);
3911
3997
  }
3998
+ console.log(`- Managed services: ${pc.bold("api, worker, realtime-gateway, admin")}`);
3912
3999
  console.log(`- Bucket: ${pc.bold(config.bucket)}`);
3913
4000
 
3914
4001
  console.log(pc.bold("\nResources"));
@@ -3969,6 +4056,7 @@ function printRailwayDeploySummary(config) {
3969
4056
  if (config.railwayContext.environmentName || config.railwayContext.environmentId) {
3970
4057
  console.log(`- Environment: ${pc.bold(config.railwayContext.environmentName || config.railwayContext.environmentId)}`);
3971
4058
  }
4059
+ console.log(`- Default managed services: ${pc.bold("api, worker, realtime-gateway, admin")}`);
3972
4060
  console.log(`- Services: ${pc.bold(config.selectedServices.join(", "))}`);
3973
4061
 
3974
4062
  console.log(pc.bold("\nDeployments"));
@@ -4244,17 +4332,38 @@ async function scanProjectForManagedRailwayServices(projectDir) {
4244
4332
  const dockerfiles = [];
4245
4333
  await collectDockerfiles(projectDir, "", dockerfiles);
4246
4334
 
4247
- const serviceSpecs = dockerfiles
4335
+ const scannedSpecs = dockerfiles
4248
4336
  .map((dockerfilePath) => buildScannedRailwayServiceSpec(projectDir, dockerfilePath))
4249
4337
  .filter(Boolean)
4250
4338
  .sort((left, right) => left.directory.localeCompare(right.directory));
4251
4339
 
4340
+ const serviceSpecs = synthesizeDerivedRailwayServices(scannedSpecs);
4341
+
4252
4342
  return {
4253
4343
  dockerfiles,
4254
4344
  serviceSpecs,
4255
4345
  };
4256
4346
  }
4257
4347
 
4348
+ function synthesizeDerivedRailwayServices(serviceSpecs) {
4349
+ const nextSpecs = [...serviceSpecs];
4350
+ const hasAPI = serviceSpecs.some((spec) => spec.key === "api" && spec.directory === "api");
4351
+ const hasWorker = serviceSpecs.some((spec) => spec.key === "worker");
4352
+ if (hasAPI && !hasWorker) {
4353
+ nextSpecs.push({
4354
+ aliases: ["worker", "api-worker"],
4355
+ baseName: "worker",
4356
+ directory: "api",
4357
+ dockerfile: "api/Dockerfile",
4358
+ key: "worker",
4359
+ seedImage: "alpine:3.22",
4360
+ serviceName: null,
4361
+ });
4362
+ }
4363
+
4364
+ return nextSpecs.sort((left, right) => `${left.directory}:${left.key}`.localeCompare(`${right.directory}:${right.key}`));
4365
+ }
4366
+
4258
4367
  async function collectDockerfiles(projectDir, relativeDir, results) {
4259
4368
  const absoluteDir = path.join(projectDir, relativeDir);
4260
4369
  const entries = await fs.readdir(absoluteDir, { withFileTypes: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-asaje-go-vue",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "CLI to scaffold, configure, and run the Asaje Go + Vue boilerplate",
5
5
  "type": "module",
6
6
  "bin": {