create-asaje-go-vue 0.2.6 → 0.2.8
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 +37 -0
- package/bin/create-asaje-go-vue.js +502 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,6 +44,14 @@ npx -p create-asaje-go-vue@latest asaje doctor ./my-app
|
|
|
44
44
|
npx -p create-asaje-go-vue@latest asaje publish
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
### Update an existing project from the template
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx -p create-asaje-go-vue@latest asaje update ./my-app --dry-run
|
|
51
|
+
npx -p create-asaje-go-vue@latest asaje update ./my-app --yes
|
|
52
|
+
npx -p create-asaje-go-vue@latest asaje update ./my-app --include admin/src/stores/session.ts,admin/src/services/http/session.ts
|
|
53
|
+
```
|
|
54
|
+
|
|
47
55
|
### Provision Railway resources
|
|
48
56
|
|
|
49
57
|
```bash
|
|
@@ -58,6 +66,13 @@ npx -p create-asaje-go-vue@latest asaje sync-railway-env ./my-app
|
|
|
58
66
|
npx -p create-asaje-go-vue@latest asaje sync-railway-env ./my-app --dry-run
|
|
59
67
|
```
|
|
60
68
|
|
|
69
|
+
### Destroy Railway resources
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx -p create-asaje-go-vue@latest asaje destroy-railway ./my-app
|
|
73
|
+
npx -p create-asaje-go-vue@latest asaje destroy-railway ./my-app --scope project --yes
|
|
74
|
+
```
|
|
75
|
+
|
|
61
76
|
## What `create` does
|
|
62
77
|
|
|
63
78
|
- clones the boilerplate from GitHub with `degit`
|
|
@@ -89,6 +104,16 @@ npx -p create-asaje-go-vue@latest asaje sync-railway-env ./my-app --dry-run
|
|
|
89
104
|
- runs `npm run pack:dry-run`
|
|
90
105
|
- prints the final manual npm release steps
|
|
91
106
|
|
|
107
|
+
## What `asaje update` does
|
|
108
|
+
|
|
109
|
+
- validates the target project structure
|
|
110
|
+
- reads the template repository and branch from `asaje.config.json` when available
|
|
111
|
+
- clones the latest template into a temporary directory
|
|
112
|
+
- overwrites a safe set of boilerplate-managed files such as Railway config, Dockerfiles, generated Swagger docs, and `.env.example` files
|
|
113
|
+
- supports `--include` for explicitly overwriting extra files or directories from the template, such as `admin/src/stores/session.ts`
|
|
114
|
+
- supports `--dry-run` to preview which files would be updated
|
|
115
|
+
- updates `asaje.config.json` with the template repository and branch used for the update
|
|
116
|
+
|
|
92
117
|
## What `asaje setup-railway` does
|
|
93
118
|
|
|
94
119
|
- validates the target project structure
|
|
@@ -111,6 +136,15 @@ npx -p create-asaje-go-vue@latest asaje sync-railway-env ./my-app --dry-run
|
|
|
111
136
|
- syncs variables for `api`, `realtime-gateway`, and `admin` without provisioning infra resources
|
|
112
137
|
- supports `--dry-run` to preview variable changes without applying them
|
|
113
138
|
|
|
139
|
+
## What `asaje destroy-railway` does
|
|
140
|
+
|
|
141
|
+
- validates the target project structure
|
|
142
|
+
- checks that the Railway CLI is installed and authenticated
|
|
143
|
+
- deletes either the linked Railway environment or the whole Railway project
|
|
144
|
+
- supports `--scope environment` (default) and `--scope project`
|
|
145
|
+
- supports `--dry-run` to preview the destructive action without applying it
|
|
146
|
+
- removes the local `asaje.railway.json` manifest after a successful deletion
|
|
147
|
+
|
|
114
148
|
## Useful flags
|
|
115
149
|
|
|
116
150
|
```bash
|
|
@@ -121,9 +155,12 @@ node ./bin/asaje.js start ../my-app --yes --profile frontend-only
|
|
|
121
155
|
node ./bin/asaje.js start ../my-app --yes --skip-admin --skip-worker
|
|
122
156
|
node ./bin/asaje.js doctor ../my-app
|
|
123
157
|
node ./bin/asaje.js publish .
|
|
158
|
+
node ./bin/asaje.js update ../my-app --dry-run
|
|
159
|
+
node ./bin/asaje.js update ../my-app --include admin/src/stores/session.ts --yes
|
|
124
160
|
node ./bin/asaje.js setup-railway ../my-app --yes
|
|
125
161
|
node ./bin/asaje.js setup-railway ../my-app --yes --dry-run
|
|
126
162
|
node ./bin/asaje.js sync-railway-env ../my-app --yes
|
|
163
|
+
node ./bin/asaje.js destroy-railway ../my-app --scope environment --yes
|
|
127
164
|
```
|
|
128
165
|
|
|
129
166
|
## Publish checklist
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
|
+
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import process from "node:process";
|
|
6
7
|
import {
|
|
@@ -46,6 +47,20 @@ const RAILWAY_APP_SERVICE_SPECS = [
|
|
|
46
47
|
serviceName: "realtime-gateway",
|
|
47
48
|
},
|
|
48
49
|
];
|
|
50
|
+
const SAFE_UPDATE_PATHS = [
|
|
51
|
+
"docker-compose.yml",
|
|
52
|
+
"admin/.env.example",
|
|
53
|
+
"admin/Dockerfile",
|
|
54
|
+
"admin/railway.json",
|
|
55
|
+
"admin/nginx",
|
|
56
|
+
"api/.env.example",
|
|
57
|
+
"api/Dockerfile",
|
|
58
|
+
"api/railway.json",
|
|
59
|
+
"api/docs",
|
|
60
|
+
"realtime-gateway/.env.example",
|
|
61
|
+
"realtime-gateway/Dockerfile",
|
|
62
|
+
"realtime-gateway/railway.json",
|
|
63
|
+
];
|
|
49
64
|
const ENV_FILE_SPECS = [
|
|
50
65
|
{ envPath: "admin/.env", examplePath: "admin/.env.example" },
|
|
51
66
|
{ envPath: "api/.env", examplePath: "api/.env.example" },
|
|
@@ -83,6 +98,12 @@ async function main() {
|
|
|
83
98
|
return;
|
|
84
99
|
}
|
|
85
100
|
|
|
101
|
+
if (invocation.command === "update") {
|
|
102
|
+
await runUpdate(invocation.argv);
|
|
103
|
+
outro(pc.green("Project update complete."));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
86
107
|
if (invocation.command === "setup-railway") {
|
|
87
108
|
await runSetupRailway(invocation.argv);
|
|
88
109
|
outro(pc.green("Railway setup complete."));
|
|
@@ -95,6 +116,12 @@ async function main() {
|
|
|
95
116
|
return;
|
|
96
117
|
}
|
|
97
118
|
|
|
119
|
+
if (invocation.command === "destroy-railway") {
|
|
120
|
+
await runDestroyRailway(invocation.argv);
|
|
121
|
+
outro(pc.green("Railway teardown complete."));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
98
125
|
await runCreate(invocation.argv);
|
|
99
126
|
outro(pc.green("Project ready."));
|
|
100
127
|
} catch (error) {
|
|
@@ -135,6 +162,10 @@ function resolveInvocation(argv) {
|
|
|
135
162
|
return { argv: rawArgs.slice(1), command: "publish", title: "asaje publish" };
|
|
136
163
|
}
|
|
137
164
|
|
|
165
|
+
if (firstArg === "update") {
|
|
166
|
+
return { argv: rawArgs.slice(1), command: "update", title: "asaje update" };
|
|
167
|
+
}
|
|
168
|
+
|
|
138
169
|
if (firstArg === "setup-railway") {
|
|
139
170
|
return { argv: rawArgs.slice(1), command: "setup-railway", title: "asaje setup-railway" };
|
|
140
171
|
}
|
|
@@ -143,6 +174,10 @@ function resolveInvocation(argv) {
|
|
|
143
174
|
return { argv: rawArgs.slice(1), command: "sync-railway-env", title: "asaje sync-railway-env" };
|
|
144
175
|
}
|
|
145
176
|
|
|
177
|
+
if (firstArg === "destroy-railway") {
|
|
178
|
+
return { argv: rawArgs.slice(1), command: "destroy-railway", title: "asaje destroy-railway" };
|
|
179
|
+
}
|
|
180
|
+
|
|
146
181
|
if (firstArg === "create") {
|
|
147
182
|
return { argv: rawArgs.slice(1), command: "create", title: "asaje create" };
|
|
148
183
|
}
|
|
@@ -162,6 +197,22 @@ function resolveInvocation(argv) {
|
|
|
162
197
|
return { argv: rawArgs.slice(1), command: "publish", title: "create-asaje-go-vue" };
|
|
163
198
|
}
|
|
164
199
|
|
|
200
|
+
if (firstArg === "update") {
|
|
201
|
+
return { argv: rawArgs.slice(1), command: "update", title: "create-asaje-go-vue" };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (firstArg === "setup-railway") {
|
|
205
|
+
return { argv: rawArgs.slice(1), command: "setup-railway", title: "create-asaje-go-vue" };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (firstArg === "sync-railway-env") {
|
|
209
|
+
return { argv: rawArgs.slice(1), command: "sync-railway-env", title: "create-asaje-go-vue" };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (firstArg === "destroy-railway") {
|
|
213
|
+
return { argv: rawArgs.slice(1), command: "destroy-railway", title: "create-asaje-go-vue" };
|
|
214
|
+
}
|
|
215
|
+
|
|
165
216
|
return { argv: rawArgs, command: "create", title: "create-asaje-go-vue" };
|
|
166
217
|
}
|
|
167
218
|
|
|
@@ -172,16 +223,20 @@ function printHelp() {
|
|
|
172
223
|
console.log(`- ${pc.bold("asaje start [directory]")} start a configured project`);
|
|
173
224
|
console.log(`- ${pc.bold("asaje doctor [directory]")} check environment and project readiness`);
|
|
174
225
|
console.log(`- ${pc.bold("asaje publish")} run npm publish checklist for the CLI package`);
|
|
226
|
+
console.log(`- ${pc.bold("asaje update [directory]")} update managed boilerplate files from the template`);
|
|
175
227
|
console.log(`- ${pc.bold("asaje setup-railway [directory]")} provision Railway infrastructure for a project`);
|
|
176
228
|
console.log(`- ${pc.bold("asaje sync-railway-env [directory]")} sync Railway app variables without provisioning`);
|
|
229
|
+
console.log(`- ${pc.bold("asaje destroy-railway [directory]")} delete the linked Railway environment or project`);
|
|
177
230
|
console.log(pc.bold("\nExamples"));
|
|
178
231
|
console.log(`- ${pc.bold("npx create-asaje-go-vue my-app")}`);
|
|
179
232
|
console.log(`- ${pc.bold("node ./bin/create-asaje-go-vue.js my-app --yes")}`);
|
|
180
233
|
console.log(`- ${pc.bold("node ./bin/asaje.js start ../my-app")}`);
|
|
181
234
|
console.log(`- ${pc.bold("node ./bin/asaje.js doctor ..")}`);
|
|
182
235
|
console.log(`- ${pc.bold("node ./bin/asaje.js publish")}`);
|
|
236
|
+
console.log(`- ${pc.bold("node ./bin/asaje.js update .. --dry-run")}`);
|
|
183
237
|
console.log(`- ${pc.bold("node ./bin/asaje.js setup-railway ..")}`);
|
|
184
238
|
console.log(`- ${pc.bold("node ./bin/asaje.js sync-railway-env ..")}`);
|
|
239
|
+
console.log(`- ${pc.bold("node ./bin/asaje.js destroy-railway ..")}`);
|
|
185
240
|
}
|
|
186
241
|
|
|
187
242
|
async function runCreate(argv) {
|
|
@@ -276,6 +331,71 @@ function parseCreateArgs(argv) {
|
|
|
276
331
|
return { ...options, directory: positionals[0] };
|
|
277
332
|
}
|
|
278
333
|
|
|
334
|
+
function parseUpdateArgs(argv) {
|
|
335
|
+
const options = {
|
|
336
|
+
branch: undefined,
|
|
337
|
+
directory: ".",
|
|
338
|
+
dryRun: false,
|
|
339
|
+
include: [],
|
|
340
|
+
template: undefined,
|
|
341
|
+
yes: false,
|
|
342
|
+
};
|
|
343
|
+
const positionals = [];
|
|
344
|
+
|
|
345
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
346
|
+
const arg = argv[index];
|
|
347
|
+
|
|
348
|
+
if (arg === "--yes" || arg === "-y") {
|
|
349
|
+
options.yes = true;
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (arg === "--dry-run") {
|
|
354
|
+
options.dryRun = true;
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (arg === "--template") {
|
|
359
|
+
options.template = argv[index + 1] || options.template;
|
|
360
|
+
index += 1;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (arg.startsWith("--template=")) {
|
|
365
|
+
options.template = arg.split("=")[1] || options.template;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (arg === "--branch") {
|
|
370
|
+
options.branch = argv[index + 1] || options.branch;
|
|
371
|
+
index += 1;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (arg.startsWith("--branch=")) {
|
|
376
|
+
options.branch = arg.split("=")[1] || options.branch;
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (arg === "--include") {
|
|
381
|
+
options.include.push(...splitCommaSeparatedPaths(argv[index + 1] || ""));
|
|
382
|
+
index += 1;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (arg.startsWith("--include=")) {
|
|
387
|
+
options.include.push(...splitCommaSeparatedPaths(arg.split("=")[1] || ""));
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
positionals.push(arg);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
options.directory = positionals[0] || options.directory;
|
|
395
|
+
options.include = uniquePaths(options.include);
|
|
396
|
+
return options;
|
|
397
|
+
}
|
|
398
|
+
|
|
279
399
|
async function collectCreateAnswers(args) {
|
|
280
400
|
const defaultDirectory = args.directory || "my-asaje-app";
|
|
281
401
|
const defaultSlug = slugify(path.basename(defaultDirectory));
|
|
@@ -610,6 +730,44 @@ async function collectCreateAnswers(args) {
|
|
|
610
730
|
});
|
|
611
731
|
}
|
|
612
732
|
|
|
733
|
+
async function collectUpdateAnswers(args) {
|
|
734
|
+
if (args.yes) {
|
|
735
|
+
return args;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const directory = await prompt(
|
|
739
|
+
text({
|
|
740
|
+
defaultValue: args.directory,
|
|
741
|
+
message: "Project directory to update?",
|
|
742
|
+
placeholder: ".",
|
|
743
|
+
validate(value) {
|
|
744
|
+
return value.trim().length === 0 ? "Project directory is required" : undefined;
|
|
745
|
+
},
|
|
746
|
+
}),
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
const include = args.include.length
|
|
750
|
+
? args.include
|
|
751
|
+
: splitCommaSeparatedPaths(
|
|
752
|
+
await prompt(
|
|
753
|
+
text({
|
|
754
|
+
defaultValue: "",
|
|
755
|
+
message: "Additional files or directories to overwrite from the template? (optional, comma-separated)",
|
|
756
|
+
placeholder: "admin/src/stores/session.ts,admin/src/services/http/session.ts",
|
|
757
|
+
}),
|
|
758
|
+
),
|
|
759
|
+
);
|
|
760
|
+
|
|
761
|
+
return {
|
|
762
|
+
branch: args.branch,
|
|
763
|
+
directory,
|
|
764
|
+
dryRun: args.dryRun,
|
|
765
|
+
include,
|
|
766
|
+
template: args.template,
|
|
767
|
+
yes: true,
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
613
771
|
function buildCreateAnswers(input) {
|
|
614
772
|
const directory = input.directory.trim();
|
|
615
773
|
const slug = slugify(path.basename(directory));
|
|
@@ -911,6 +1069,48 @@ async function runPublish(argv) {
|
|
|
911
1069
|
console.log(`- Publish with ${pc.bold("npm publish")}`);
|
|
912
1070
|
}
|
|
913
1071
|
|
|
1072
|
+
async function runUpdate(argv) {
|
|
1073
|
+
const args = parseUpdateArgs(argv);
|
|
1074
|
+
const answers = await collectUpdateAnswers(args);
|
|
1075
|
+
const projectDir = path.resolve(process.cwd(), answers.directory);
|
|
1076
|
+
|
|
1077
|
+
await ensureProjectStructure(projectDir);
|
|
1078
|
+
|
|
1079
|
+
const projectConfig = await loadProjectConfig(projectDir);
|
|
1080
|
+
const templateRepository = answers.template || projectConfig?.template?.repository || DEFAULT_TEMPLATE;
|
|
1081
|
+
const templateBranch = answers.branch || projectConfig?.template?.branch || DEFAULT_BRANCH;
|
|
1082
|
+
const templateDir = await fs.mkdtemp(path.join(os.tmpdir(), "asaje-update-"));
|
|
1083
|
+
|
|
1084
|
+
try {
|
|
1085
|
+
console.log(pc.dim(`\nCloning template ${templateRepository}#${templateBranch}...`));
|
|
1086
|
+
await cloneTemplate(templateRepository, templateBranch, templateDir);
|
|
1087
|
+
await cleanupTemplateFiles(templateDir);
|
|
1088
|
+
|
|
1089
|
+
const selectedPaths = uniquePaths([...SAFE_UPDATE_PATHS, ...answers.include]);
|
|
1090
|
+
const summary = await applyTemplateUpdates({
|
|
1091
|
+
dryRun: answers.dryRun,
|
|
1092
|
+
projectDir,
|
|
1093
|
+
selectedPaths,
|
|
1094
|
+
templateDir,
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
if (!answers.dryRun) {
|
|
1098
|
+
await updateProjectTemplateConfig(projectDir, projectConfig, templateRepository, templateBranch);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
printUpdateSummary({
|
|
1102
|
+
branch: templateBranch,
|
|
1103
|
+
dryRun: answers.dryRun,
|
|
1104
|
+
include: answers.include,
|
|
1105
|
+
projectDir,
|
|
1106
|
+
repository: templateRepository,
|
|
1107
|
+
summary,
|
|
1108
|
+
});
|
|
1109
|
+
} finally {
|
|
1110
|
+
await fs.remove(templateDir);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
914
1114
|
async function runSetupRailway(argv) {
|
|
915
1115
|
const args = parseSetupRailwayArgs(argv);
|
|
916
1116
|
const answers = await collectSetupRailwayAnswers(args);
|
|
@@ -1098,6 +1298,67 @@ async function runSyncRailwayEnv(argv) {
|
|
|
1098
1298
|
});
|
|
1099
1299
|
}
|
|
1100
1300
|
|
|
1301
|
+
async function runDestroyRailway(argv) {
|
|
1302
|
+
const args = parseDestroyRailwayArgs(argv);
|
|
1303
|
+
const answers = await collectDestroyRailwayAnswers(args);
|
|
1304
|
+
const projectDir = path.resolve(process.cwd(), answers.directory);
|
|
1305
|
+
|
|
1306
|
+
await ensureProjectStructure(projectDir);
|
|
1307
|
+
await ensureRailwayCliInstalled();
|
|
1308
|
+
await ensureRailwayAuthenticated(projectDir, answers.environment);
|
|
1309
|
+
|
|
1310
|
+
const railwayContext = await loadRailwayContext(projectDir, answers.environment);
|
|
1311
|
+
const targetEnvironment = answers.environment || railwayContext.environmentId || railwayContext.environmentName;
|
|
1312
|
+
const targetProject = railwayContext.projectId || railwayContext.projectName;
|
|
1313
|
+
|
|
1314
|
+
if (answers.scope === "project") {
|
|
1315
|
+
if (!targetProject) {
|
|
1316
|
+
throw new Error(`Unable to determine Railway project for ${projectDir}. Link the directory first with \`railway link\`.`);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
console.log(pc.bold("\nDestroy Railway project"));
|
|
1320
|
+
console.log(`- Project: ${pc.bold(railwayContext.projectName || railwayContext.projectId)}`);
|
|
1321
|
+
if (answers.dryRun) {
|
|
1322
|
+
console.log(`- Dry run: would delete project ${pc.bold(targetProject)}`);
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
const commandArgs = ["project", "delete", "--project", targetProject, "--yes"];
|
|
1327
|
+
if (answers.twoFactorCode) {
|
|
1328
|
+
commandArgs.push("--2fa-code", answers.twoFactorCode);
|
|
1329
|
+
}
|
|
1330
|
+
await runRailwayCommand(projectDir, undefined, commandArgs);
|
|
1331
|
+
} else {
|
|
1332
|
+
if (!targetEnvironment) {
|
|
1333
|
+
throw new Error(`Unable to determine Railway environment for ${projectDir}. Pass \`--environment\` or link the directory first.`);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
await ensureRailwayEnvironmentLinked(projectDir, targetEnvironment);
|
|
1337
|
+
|
|
1338
|
+
console.log(pc.bold("\nDestroy Railway environment"));
|
|
1339
|
+
console.log(`- Environment: ${pc.bold(targetEnvironment)}`);
|
|
1340
|
+
if (railwayContext.projectName || railwayContext.projectId) {
|
|
1341
|
+
console.log(`- Project: ${pc.bold(railwayContext.projectName || railwayContext.projectId)}`);
|
|
1342
|
+
}
|
|
1343
|
+
if (answers.dryRun) {
|
|
1344
|
+
console.log(`- Dry run: would delete environment ${pc.bold(targetEnvironment)}`);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
const commandArgs = ["environment", "delete", targetEnvironment, "--yes"];
|
|
1349
|
+
if (answers.twoFactorCode) {
|
|
1350
|
+
commandArgs.push("--2fa-code", answers.twoFactorCode);
|
|
1351
|
+
}
|
|
1352
|
+
await runRailwayCommand(projectDir, undefined, commandArgs);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
const manifestPath = path.join(projectDir, RAILWAY_MANIFEST_FILENAME);
|
|
1356
|
+
if (await fs.pathExists(manifestPath)) {
|
|
1357
|
+
await fs.remove(manifestPath);
|
|
1358
|
+
console.log(`- Removed local ${pc.bold(RAILWAY_MANIFEST_FILENAME)} manifest`);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1101
1362
|
function parseDirectoryArgs(argv) {
|
|
1102
1363
|
return { directory: argv[0] || "." };
|
|
1103
1364
|
}
|
|
@@ -1154,6 +1415,74 @@ function parseSetupRailwayArgs(argv) {
|
|
|
1154
1415
|
return options;
|
|
1155
1416
|
}
|
|
1156
1417
|
|
|
1418
|
+
function parseDestroyRailwayArgs(argv) {
|
|
1419
|
+
const options = {
|
|
1420
|
+
directory: ".",
|
|
1421
|
+
dryRun: false,
|
|
1422
|
+
environment: undefined,
|
|
1423
|
+
scope: "environment",
|
|
1424
|
+
twoFactorCode: undefined,
|
|
1425
|
+
yes: false,
|
|
1426
|
+
};
|
|
1427
|
+
const positionals = [];
|
|
1428
|
+
|
|
1429
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1430
|
+
const arg = argv[index];
|
|
1431
|
+
|
|
1432
|
+
if (arg === "--yes" || arg === "-y") {
|
|
1433
|
+
options.yes = true;
|
|
1434
|
+
continue;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
if (arg === "--dry-run") {
|
|
1438
|
+
options.dryRun = true;
|
|
1439
|
+
continue;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
if (arg === "--environment" || arg === "-e") {
|
|
1443
|
+
options.environment = argv[index + 1] || options.environment;
|
|
1444
|
+
index += 1;
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
if (arg.startsWith("--environment=")) {
|
|
1449
|
+
options.environment = arg.split("=")[1] || options.environment;
|
|
1450
|
+
continue;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
if (arg === "--scope") {
|
|
1454
|
+
options.scope = argv[index + 1] || options.scope;
|
|
1455
|
+
index += 1;
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
if (arg.startsWith("--scope=")) {
|
|
1460
|
+
options.scope = arg.split("=")[1] || options.scope;
|
|
1461
|
+
continue;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
if (arg === "--2fa-code") {
|
|
1465
|
+
options.twoFactorCode = argv[index + 1] || options.twoFactorCode;
|
|
1466
|
+
index += 1;
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
if (arg.startsWith("--2fa-code=")) {
|
|
1471
|
+
options.twoFactorCode = arg.split("=")[1] || options.twoFactorCode;
|
|
1472
|
+
continue;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
positionals.push(arg);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
options.directory = positionals[0] || options.directory;
|
|
1479
|
+
if (!["environment", "project"].includes(options.scope)) {
|
|
1480
|
+
throw new Error("--scope must be either 'environment' or 'project'");
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
return options;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1157
1486
|
async function collectSetupRailwayAnswers(args) {
|
|
1158
1487
|
if (args.yes) {
|
|
1159
1488
|
return {
|
|
@@ -1207,6 +1536,68 @@ async function collectSetupRailwayAnswers(args) {
|
|
|
1207
1536
|
};
|
|
1208
1537
|
}
|
|
1209
1538
|
|
|
1539
|
+
async function collectDestroyRailwayAnswers(args) {
|
|
1540
|
+
if (args.yes) {
|
|
1541
|
+
return args;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
const directory = await prompt(
|
|
1545
|
+
text({
|
|
1546
|
+
defaultValue: args.directory,
|
|
1547
|
+
message: "Project directory linked to Railway?",
|
|
1548
|
+
placeholder: ".",
|
|
1549
|
+
validate(value) {
|
|
1550
|
+
return value.trim().length === 0 ? "Project directory is required" : undefined;
|
|
1551
|
+
},
|
|
1552
|
+
}),
|
|
1553
|
+
);
|
|
1554
|
+
|
|
1555
|
+
const scope = await prompt(
|
|
1556
|
+
select({
|
|
1557
|
+
initialValue: args.scope,
|
|
1558
|
+
message: "What should be deleted?",
|
|
1559
|
+
options: [
|
|
1560
|
+
{ label: "Environment", value: "environment", hint: "Delete one Railway environment and its services/resources" },
|
|
1561
|
+
{ label: "Project", value: "project", hint: "Delete the whole Railway project" },
|
|
1562
|
+
],
|
|
1563
|
+
}),
|
|
1564
|
+
);
|
|
1565
|
+
|
|
1566
|
+
let environment = args.environment;
|
|
1567
|
+
if (scope === "environment" && !environment) {
|
|
1568
|
+
environment = await prompt(
|
|
1569
|
+
text({
|
|
1570
|
+
defaultValue: "",
|
|
1571
|
+
message: "Railway environment name or ID? (leave empty for linked default)",
|
|
1572
|
+
placeholder: "production",
|
|
1573
|
+
}),
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
const confirmed = await prompt(
|
|
1578
|
+
confirm({
|
|
1579
|
+
initialValue: false,
|
|
1580
|
+
message:
|
|
1581
|
+
scope === "project"
|
|
1582
|
+
? "Delete the whole Railway project and all its environments?"
|
|
1583
|
+
: `Delete the Railway environment${environment ? ` ${environment}` : ""} and all its services/resources?`,
|
|
1584
|
+
}),
|
|
1585
|
+
);
|
|
1586
|
+
|
|
1587
|
+
if (!confirmed) {
|
|
1588
|
+
throw new Error("Railway teardown cancelled.");
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
return {
|
|
1592
|
+
directory,
|
|
1593
|
+
dryRun: args.dryRun,
|
|
1594
|
+
environment: environment?.trim() || undefined,
|
|
1595
|
+
scope,
|
|
1596
|
+
twoFactorCode: args.twoFactorCode,
|
|
1597
|
+
yes: true,
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1210
1601
|
async function ensureRailwayCliInstalled() {
|
|
1211
1602
|
const result = await checkCommand({ command: "railway", args: ["--version"], name: "railway" });
|
|
1212
1603
|
if (!result.ok) {
|
|
@@ -1504,6 +1895,8 @@ async function ensureRailwayAppService(config) {
|
|
|
1504
1895
|
config.serviceName,
|
|
1505
1896
|
"--image",
|
|
1506
1897
|
config.seedImage || "alpine:3.22",
|
|
1898
|
+
"--variables",
|
|
1899
|
+
`ASAJE_BOOTSTRAP_SERVICE=${config.serviceName}`,
|
|
1507
1900
|
]);
|
|
1508
1901
|
}
|
|
1509
1902
|
|
|
@@ -2399,6 +2792,115 @@ async function ensureProjectStructure(projectDir) {
|
|
|
2399
2792
|
}
|
|
2400
2793
|
}
|
|
2401
2794
|
|
|
2795
|
+
async function loadProjectConfig(projectDir) {
|
|
2796
|
+
const configPath = path.join(projectDir, "asaje.config.json");
|
|
2797
|
+
if (!(await fs.pathExists(configPath))) {
|
|
2798
|
+
return null;
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
return fs.readJson(configPath);
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
async function updateProjectTemplateConfig(projectDir, projectConfig, templateRepository, templateBranch) {
|
|
2805
|
+
const configPath = path.join(projectDir, "asaje.config.json");
|
|
2806
|
+
const nextConfig = {
|
|
2807
|
+
...(projectConfig || {}),
|
|
2808
|
+
template: {
|
|
2809
|
+
branch: templateBranch,
|
|
2810
|
+
repository: templateRepository,
|
|
2811
|
+
},
|
|
2812
|
+
};
|
|
2813
|
+
|
|
2814
|
+
await fs.writeJson(configPath, nextConfig, { spaces: 2 });
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
async function applyTemplateUpdates(config) {
|
|
2818
|
+
const summary = {
|
|
2819
|
+
missing: [],
|
|
2820
|
+
skipped: [],
|
|
2821
|
+
updated: [],
|
|
2822
|
+
};
|
|
2823
|
+
|
|
2824
|
+
for (const relativePath of config.selectedPaths) {
|
|
2825
|
+
const sourcePath = path.join(config.templateDir, relativePath);
|
|
2826
|
+
const destinationPath = path.join(config.projectDir, relativePath);
|
|
2827
|
+
|
|
2828
|
+
if (!(await fs.pathExists(sourcePath))) {
|
|
2829
|
+
summary.missing.push(relativePath);
|
|
2830
|
+
continue;
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
const sourceStats = await fs.stat(sourcePath);
|
|
2834
|
+
if (config.dryRun) {
|
|
2835
|
+
summary.updated.push(relativePath);
|
|
2836
|
+
continue;
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
if (sourceStats.isDirectory()) {
|
|
2840
|
+
await fs.remove(destinationPath);
|
|
2841
|
+
await fs.copy(sourcePath, destinationPath);
|
|
2842
|
+
} else {
|
|
2843
|
+
await fs.ensureDir(path.dirname(destinationPath));
|
|
2844
|
+
await fs.copyFile(sourcePath, destinationPath);
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
summary.updated.push(relativePath);
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
summary.skipped = config.selectedPaths.filter((relativePath) => !summary.updated.includes(relativePath) && !summary.missing.includes(relativePath));
|
|
2851
|
+
return summary;
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
function printUpdateSummary(config) {
|
|
2855
|
+
console.log(pc.bold("\nUpdate"));
|
|
2856
|
+
console.log(`- Directory: ${pc.bold(config.projectDir)}`);
|
|
2857
|
+
console.log(`- Template: ${pc.bold(`${config.repository}#${config.branch}`)}`);
|
|
2858
|
+
console.log(`- Safe paths: ${pc.bold(String(SAFE_UPDATE_PATHS.length))}`);
|
|
2859
|
+
if (config.include.length > 0) {
|
|
2860
|
+
console.log(`- Extra include: ${pc.bold(config.include.join(", "))}`);
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
console.log(pc.bold("\nUpdated"));
|
|
2864
|
+
if (config.summary.updated.length === 0) {
|
|
2865
|
+
console.log("- No files selected for update");
|
|
2866
|
+
} else {
|
|
2867
|
+
for (const relativePath of config.summary.updated) {
|
|
2868
|
+
console.log(`- ${config.dryRun ? "would update" : "updated"} ${pc.bold(relativePath)}`);
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
if (config.summary.missing.length > 0) {
|
|
2873
|
+
console.log(pc.bold("\nMissing In Template"));
|
|
2874
|
+
for (const relativePath of config.summary.missing) {
|
|
2875
|
+
console.log(`- ${pc.bold(relativePath)}`);
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
if (config.dryRun) {
|
|
2880
|
+
console.log("- Dry run only, local files were not modified");
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
function splitCommaSeparatedPaths(value) {
|
|
2885
|
+
return value
|
|
2886
|
+
.split(",")
|
|
2887
|
+
.map((entry) => normalizeRelativePath(entry))
|
|
2888
|
+
.filter(Boolean);
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
function uniquePaths(paths) {
|
|
2892
|
+
return [...new Set(paths.map((entry) => normalizeRelativePath(entry)).filter(Boolean))];
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
function normalizeRelativePath(value) {
|
|
2896
|
+
const trimmed = String(value || "").trim();
|
|
2897
|
+
if (!trimmed) {
|
|
2898
|
+
return "";
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
return trimmed.replace(/^\.\//, "").replace(/\\/g, "/").replace(/\/$/, "");
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2402
2904
|
async function ensureEnvFiles(projectDir) {
|
|
2403
2905
|
for (const spec of ENV_FILE_SPECS) {
|
|
2404
2906
|
const envPath = path.join(projectDir, spec.envPath);
|