@open-mercato/cli 0.6.5-develop.5116.1.f0af9e5080 → 0.6.5-develop.5155.1.148d10a46d
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/.turbo/turbo-build.log +1 -1
- package/dist/bin.js +1 -0
- package/dist/bin.js.map +2 -2
- package/dist/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.meta.js +7 -0
- package/dist/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.meta.js.map +7 -0
- package/dist/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.spec.js +63 -0
- package/dist/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.spec.js.map +7 -0
- package/dist/lib/deploy/railway/env.js +98 -0
- package/dist/lib/deploy/railway/env.js.map +7 -0
- package/dist/lib/deploy/railway/graphql-client.js +99 -0
- package/dist/lib/deploy/railway/graphql-client.js.map +7 -0
- package/dist/lib/deploy/railway/index.js +1115 -0
- package/dist/lib/deploy/railway/index.js.map +7 -0
- package/dist/lib/deploy/railway/operations.js +249 -0
- package/dist/lib/deploy/railway/operations.js.map +7 -0
- package/dist/lib/deploy/railway/options.js +155 -0
- package/dist/lib/deploy/railway/options.js.map +7 -0
- package/dist/lib/deploy/railway/railway-cli.js +61 -0
- package/dist/lib/deploy/railway/railway-cli.js.map +7 -0
- package/dist/lib/deploy/railway/redaction.js +88 -0
- package/dist/lib/deploy/railway/redaction.js.map +7 -0
- package/dist/lib/deploy/railway/source.js +128 -0
- package/dist/lib/deploy/railway/source.js.map +7 -0
- package/dist/lib/deploy/railway/state.js +41 -0
- package/dist/lib/deploy/railway/state.js.map +7 -0
- package/dist/lib/deploy/railway/token.js +46 -0
- package/dist/lib/deploy/railway/token.js.map +7 -0
- package/dist/lib/deploy/railway/types.js +1 -0
- package/dist/lib/deploy/railway/types.js.map +7 -0
- package/dist/mercato.js +23 -8
- package/dist/mercato.js.map +2 -2
- package/package.json +10 -10
- package/src/bin.ts +1 -0
- package/src/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.meta.ts +3 -0
- package/src/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.spec.ts +71 -0
- package/src/lib/deploy/railway/__tests__/dry-run.test.ts +61 -0
- package/src/lib/deploy/railway/__tests__/env.test.ts +55 -0
- package/src/lib/deploy/railway/__tests__/graphql-client.test.ts +80 -0
- package/src/lib/deploy/railway/__tests__/operations.test.ts +21 -0
- package/src/lib/deploy/railway/__tests__/options.test.ts +78 -0
- package/src/lib/deploy/railway/__tests__/redaction.test.ts +62 -0
- package/src/lib/deploy/railway/__tests__/source.test.ts +130 -0
- package/src/lib/deploy/railway/__tests__/state.test.ts +24 -0
- package/src/lib/deploy/railway/__tests__/token.test.ts +39 -0
- package/src/lib/deploy/railway/env.ts +112 -0
- package/src/lib/deploy/railway/graphql-client.ts +128 -0
- package/src/lib/deploy/railway/index.ts +1454 -0
- package/src/lib/deploy/railway/operations.ts +248 -0
- package/src/lib/deploy/railway/options.ts +167 -0
- package/src/lib/deploy/railway/railway-cli.ts +65 -0
- package/src/lib/deploy/railway/redaction.ts +91 -0
- package/src/lib/deploy/railway/source.ts +143 -0
- package/src/lib/deploy/railway/state.ts +45 -0
- package/src/lib/deploy/railway/token.ts +49 -0
- package/src/lib/deploy/railway/types.ts +87 -0
- package/src/mercato.ts +33 -8
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { redactText } from "./redaction.js";
|
|
3
|
+
function runRailway(args, cwd, token, secrets) {
|
|
4
|
+
const result = spawnSync("railway", args, {
|
|
5
|
+
cwd,
|
|
6
|
+
encoding: "utf8",
|
|
7
|
+
env: { ...process.env, RAILWAY_API_TOKEN: token },
|
|
8
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
9
|
+
});
|
|
10
|
+
const output = `${result.stdout ?? ""}
|
|
11
|
+
${result.stderr ?? ""}`.trim();
|
|
12
|
+
if (result.status !== 0) {
|
|
13
|
+
throw new Error(redactText(output || `railway ${args[0] ?? ""} failed`, secrets));
|
|
14
|
+
}
|
|
15
|
+
return output;
|
|
16
|
+
}
|
|
17
|
+
function uploadLocalSource(input) {
|
|
18
|
+
runRailway(
|
|
19
|
+
[
|
|
20
|
+
"link",
|
|
21
|
+
"--project",
|
|
22
|
+
input.projectId,
|
|
23
|
+
"--environment",
|
|
24
|
+
input.environmentId,
|
|
25
|
+
"--service",
|
|
26
|
+
input.serviceId
|
|
27
|
+
],
|
|
28
|
+
input.cwd,
|
|
29
|
+
input.token,
|
|
30
|
+
[input.token]
|
|
31
|
+
);
|
|
32
|
+
const output = runRailway(
|
|
33
|
+
[
|
|
34
|
+
"up",
|
|
35
|
+
"--environment",
|
|
36
|
+
input.environmentId,
|
|
37
|
+
"--service",
|
|
38
|
+
input.serviceName,
|
|
39
|
+
"--detach",
|
|
40
|
+
"--json"
|
|
41
|
+
],
|
|
42
|
+
input.cwd,
|
|
43
|
+
input.token,
|
|
44
|
+
[input.token]
|
|
45
|
+
);
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(output);
|
|
48
|
+
if (parsed && typeof parsed === "object") {
|
|
49
|
+
const record = parsed;
|
|
50
|
+
const deploymentId = record.deploymentId ?? record.deployment_id ?? record.id;
|
|
51
|
+
return typeof deploymentId === "string" ? deploymentId : null;
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
uploadLocalSource
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=railway-cli.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/lib/deploy/railway/railway-cli.ts"],
|
|
4
|
+
"sourcesContent": ["import { spawnSync } from 'node:child_process'\nimport { redactText } from './redaction'\n\nfunction runRailway(args: string[], cwd: string, token: string, secrets: string[]): string {\n const result = spawnSync('railway', args, {\n cwd,\n encoding: 'utf8',\n env: { ...process.env, RAILWAY_API_TOKEN: token },\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n const output = `${result.stdout ?? ''}\\n${result.stderr ?? ''}`.trim()\n if (result.status !== 0) {\n throw new Error(redactText(output || `railway ${args[0] ?? ''} failed`, secrets))\n }\n return output\n}\n\nexport function uploadLocalSource(input: {\n cwd: string\n projectId: string\n environmentId: string\n serviceId: string\n serviceName: string\n token: string\n}): string | null {\n runRailway(\n [\n 'link',\n '--project',\n input.projectId,\n '--environment',\n input.environmentId,\n '--service',\n input.serviceId,\n ],\n input.cwd,\n input.token,\n [input.token],\n )\n const output = runRailway(\n [\n 'up',\n '--environment',\n input.environmentId,\n '--service',\n input.serviceName,\n '--detach',\n '--json',\n ],\n input.cwd,\n input.token,\n [input.token],\n )\n try {\n const parsed: unknown = JSON.parse(output)\n if (parsed && typeof parsed === 'object') {\n const record = parsed as Record<string, unknown>\n const deploymentId = record.deploymentId ?? record.deployment_id ?? record.id\n return typeof deploymentId === 'string' ? deploymentId : null\n }\n } catch {\n return null\n }\n return null\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB;AAE3B,SAAS,WAAW,MAAgB,KAAa,OAAe,SAA2B;AACzF,QAAM,SAAS,UAAU,WAAW,MAAM;AAAA,IACxC;AAAA,IACA,UAAU;AAAA,IACV,KAAK,EAAE,GAAG,QAAQ,KAAK,mBAAmB,MAAM;AAAA,IAChD,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,EAClC,CAAC;AACD,QAAM,SAAS,GAAG,OAAO,UAAU,EAAE;AAAA,EAAK,OAAO,UAAU,EAAE,GAAG,KAAK;AACrE,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,IAAI,MAAM,WAAW,UAAU,WAAW,KAAK,CAAC,KAAK,EAAE,WAAW,OAAO,CAAC;AAAA,EAClF;AACA,SAAO;AACT;AAEO,SAAS,kBAAkB,OAOhB;AAChB;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA,MAAM;AAAA,IACR;AAAA,IACA,MAAM;AAAA,IACN,MAAM;AAAA,IACN,CAAC,MAAM,KAAK;AAAA,EACd;AACA,QAAM,SAAS;AAAA,IACb;AAAA,MACE;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IACF;AAAA,IACA,MAAM;AAAA,IACN,MAAM;AAAA,IACN,CAAC,MAAM,KAAK;AAAA,EACd;AACA,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,MAAM;AACzC,QAAI,UAAU,OAAO,WAAW,UAAU;AACxC,YAAM,SAAS;AACf,YAAM,eAAe,OAAO,gBAAgB,OAAO,iBAAiB,OAAO;AAC3E,aAAO,OAAO,iBAAiB,WAAW,eAAe;AAAA,IAC3D;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
const SENSITIVE_KEY_PATTERN = /(secret|token|password|private|credential|encryption|fallback|api[_-]?key|database_url|redis_url)/i;
|
|
3
|
+
const CREDENTIAL_PATTERNS = [
|
|
4
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/,
|
|
5
|
+
/\bsk-[A-Za-z0-9_-]{12,}\b/,
|
|
6
|
+
/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/,
|
|
7
|
+
/\bgh[pousr]_[A-Za-z0-9]{20,}\b/,
|
|
8
|
+
/\bAKIA[0-9A-Z]{16}\b/
|
|
9
|
+
];
|
|
10
|
+
function fingerprintSecret(value) {
|
|
11
|
+
return `sha256:${createHash("sha256").update(value).digest("hex").slice(0, 8)}`;
|
|
12
|
+
}
|
|
13
|
+
function isSensitiveKey(key) {
|
|
14
|
+
return SENSITIVE_KEY_PATTERN.test(key);
|
|
15
|
+
}
|
|
16
|
+
function detectCredentialValue(value, railwayToken) {
|
|
17
|
+
if (railwayToken && value.includes(railwayToken)) return true;
|
|
18
|
+
return CREDENTIAL_PATTERNS.some((pattern) => pattern.test(value));
|
|
19
|
+
}
|
|
20
|
+
function assertSafeVariables(variables, options = {}) {
|
|
21
|
+
const allowedKeys = new Set(options.allowedKeys ?? []);
|
|
22
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
23
|
+
if (!allowedKeys.has(key) && detectCredentialValue(value, options.railwayToken)) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Refusing to upload credential-like value from ${key}. Pass --allow-secret-passthrough ${key} to allow this exact key.`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function formatVariableValue(key, value, railwayToken) {
|
|
31
|
+
if (isSensitiveKey(key) || detectCredentialValue(value, railwayToken)) {
|
|
32
|
+
return `<redacted> (${fingerprintSecret(value)})`;
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function redactText(value, secrets = []) {
|
|
37
|
+
let redacted = value;
|
|
38
|
+
for (const secret of secrets.filter(Boolean).sort((left, right) => right.length - left.length)) {
|
|
39
|
+
redacted = redacted.split(secret).join("****");
|
|
40
|
+
}
|
|
41
|
+
for (const pattern of CREDENTIAL_PATTERNS) {
|
|
42
|
+
redacted = redacted.replace(new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`), "****");
|
|
43
|
+
}
|
|
44
|
+
return redacted.replace(/Bearer\s+[A-Za-z0-9._~+/-]+/gi, "Bearer ****");
|
|
45
|
+
}
|
|
46
|
+
function redactStructuredValue(value, secrets = []) {
|
|
47
|
+
if (typeof value === "string") return redactText(value, secrets);
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
return value.map((item) => redactStructuredValue(item, secrets));
|
|
50
|
+
}
|
|
51
|
+
if (!value || typeof value !== "object") return value;
|
|
52
|
+
return Object.fromEntries(
|
|
53
|
+
Object.entries(value).map(([key, nestedValue]) => [
|
|
54
|
+
key,
|
|
55
|
+
isSensitiveKey(key) ? "<redacted>" : redactStructuredValue(nestedValue, secrets)
|
|
56
|
+
])
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
function collectSensitiveStructuredValues(value) {
|
|
60
|
+
const collected = /* @__PURE__ */ new Set();
|
|
61
|
+
function visit(current) {
|
|
62
|
+
if (Array.isArray(current)) {
|
|
63
|
+
current.forEach(visit);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!current || typeof current !== "object") return;
|
|
67
|
+
for (const [key, nestedValue] of Object.entries(current)) {
|
|
68
|
+
if (isSensitiveKey(key) && typeof nestedValue === "string" && nestedValue.length > 0) {
|
|
69
|
+
collected.add(nestedValue);
|
|
70
|
+
} else {
|
|
71
|
+
visit(nestedValue);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
visit(value);
|
|
76
|
+
return Array.from(collected);
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
assertSafeVariables,
|
|
80
|
+
collectSensitiveStructuredValues,
|
|
81
|
+
detectCredentialValue,
|
|
82
|
+
fingerprintSecret,
|
|
83
|
+
formatVariableValue,
|
|
84
|
+
isSensitiveKey,
|
|
85
|
+
redactStructuredValue,
|
|
86
|
+
redactText
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=redaction.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/lib/deploy/railway/redaction.ts"],
|
|
4
|
+
"sourcesContent": ["import { createHash } from 'node:crypto'\n\nconst SENSITIVE_KEY_PATTERN = /(secret|token|password|private|credential|encryption|fallback|api[_-]?key|database_url|redis_url)/i\nconst CREDENTIAL_PATTERNS = [\n /-----BEGIN [A-Z ]*PRIVATE KEY-----/,\n /\\bsk-[A-Za-z0-9_-]{12,}\\b/,\n /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/,\n /\\bgh[pousr]_[A-Za-z0-9]{20,}\\b/,\n /\\bAKIA[0-9A-Z]{16}\\b/,\n]\n\nexport function fingerprintSecret(value: string): string {\n return `sha256:${createHash('sha256').update(value).digest('hex').slice(0, 8)}`\n}\n\nexport function isSensitiveKey(key: string): boolean {\n return SENSITIVE_KEY_PATTERN.test(key)\n}\n\nexport function detectCredentialValue(value: string, railwayToken?: string): boolean {\n if (railwayToken && value.includes(railwayToken)) return true\n return CREDENTIAL_PATTERNS.some((pattern) => pattern.test(value))\n}\n\nexport function assertSafeVariables(\n variables: Record<string, string>,\n options: { railwayToken?: string; allowedKeys?: string[] } = {},\n): void {\n const allowedKeys = new Set(options.allowedKeys ?? [])\n for (const [key, value] of Object.entries(variables)) {\n if (!allowedKeys.has(key) && detectCredentialValue(value, options.railwayToken)) {\n throw new Error(\n `Refusing to upload credential-like value from ${key}. Pass --allow-secret-passthrough ${key} to allow this exact key.`,\n )\n }\n }\n}\n\nexport function formatVariableValue(key: string, value: string, railwayToken?: string): string {\n if (isSensitiveKey(key) || detectCredentialValue(value, railwayToken)) {\n return `<redacted> (${fingerprintSecret(value)})`\n }\n return value\n}\n\nexport function redactText(value: string, secrets: string[] = []): string {\n let redacted = value\n for (const secret of secrets.filter(Boolean).sort((left, right) => right.length - left.length)) {\n redacted = redacted.split(secret).join('****')\n }\n for (const pattern of CREDENTIAL_PATTERNS) {\n redacted = redacted.replace(new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`), '****')\n }\n return redacted.replace(/Bearer\\s+[A-Za-z0-9._~+/-]+/gi, 'Bearer ****')\n}\n\nexport function redactStructuredValue(value: unknown, secrets: string[] = []): unknown {\n if (typeof value === 'string') return redactText(value, secrets)\n if (Array.isArray(value)) {\n return value.map((item) => redactStructuredValue(item, secrets))\n }\n if (!value || typeof value !== 'object') return value\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>).map(([key, nestedValue]) => [\n key,\n isSensitiveKey(key) ? '<redacted>' : redactStructuredValue(nestedValue, secrets),\n ]),\n )\n}\n\nexport function collectSensitiveStructuredValues(value: unknown): string[] {\n const collected = new Set<string>()\n\n function visit(current: unknown): void {\n if (Array.isArray(current)) {\n current.forEach(visit)\n return\n }\n if (!current || typeof current !== 'object') return\n for (const [key, nestedValue] of Object.entries(current as Record<string, unknown>)) {\n if (isSensitiveKey(key) && typeof nestedValue === 'string' && nestedValue.length > 0) {\n collected.add(nestedValue)\n } else {\n visit(nestedValue)\n }\n }\n }\n\n visit(value)\n return Array.from(collected)\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,kBAAkB;AAE3B,MAAM,wBAAwB;AAC9B,MAAM,sBAAsB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,kBAAkB,OAAuB;AACvD,SAAO,UAAU,WAAW,QAAQ,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC;AAC/E;AAEO,SAAS,eAAe,KAAsB;AACnD,SAAO,sBAAsB,KAAK,GAAG;AACvC;AAEO,SAAS,sBAAsB,OAAe,cAAgC;AACnF,MAAI,gBAAgB,MAAM,SAAS,YAAY,EAAG,QAAO;AACzD,SAAO,oBAAoB,KAAK,CAAC,YAAY,QAAQ,KAAK,KAAK,CAAC;AAClE;AAEO,SAAS,oBACd,WACA,UAA6D,CAAC,GACxD;AACN,QAAM,cAAc,IAAI,IAAI,QAAQ,eAAe,CAAC,CAAC;AACrD,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,QAAI,CAAC,YAAY,IAAI,GAAG,KAAK,sBAAsB,OAAO,QAAQ,YAAY,GAAG;AAC/E,YAAM,IAAI;AAAA,QACR,iDAAiD,GAAG,qCAAqC,GAAG;AAAA,MAC9F;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,oBAAoB,KAAa,OAAe,cAA+B;AAC7F,MAAI,eAAe,GAAG,KAAK,sBAAsB,OAAO,YAAY,GAAG;AACrE,WAAO,eAAe,kBAAkB,KAAK,CAAC;AAAA,EAChD;AACA,SAAO;AACT;AAEO,SAAS,WAAW,OAAe,UAAoB,CAAC,GAAW;AACxE,MAAI,WAAW;AACf,aAAW,UAAU,QAAQ,OAAO,OAAO,EAAE,KAAK,CAAC,MAAM,UAAU,MAAM,SAAS,KAAK,MAAM,GAAG;AAC9F,eAAW,SAAS,MAAM,MAAM,EAAE,KAAK,MAAM;AAAA,EAC/C;AACA,aAAW,WAAW,qBAAqB;AACzC,eAAW,SAAS,QAAQ,IAAI,OAAO,QAAQ,QAAQ,QAAQ,MAAM,SAAS,GAAG,IAAI,QAAQ,QAAQ,GAAG,QAAQ,KAAK,GAAG,GAAG,MAAM;AAAA,EACnI;AACA,SAAO,SAAS,QAAQ,iCAAiC,aAAa;AACxE;AAEO,SAAS,sBAAsB,OAAgB,UAAoB,CAAC,GAAY;AACrF,MAAI,OAAO,UAAU,SAAU,QAAO,WAAW,OAAO,OAAO;AAC/D,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,IAAI,CAAC,SAAS,sBAAsB,MAAM,OAAO,CAAC;AAAA,EACjE;AACA,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,KAAgC,EAAE,IAAI,CAAC,CAAC,KAAK,WAAW,MAAM;AAAA,MAC3E;AAAA,MACA,eAAe,GAAG,IAAI,eAAe,sBAAsB,aAAa,OAAO;AAAA,IACjF,CAAC;AAAA,EACH;AACF;AAEO,SAAS,iCAAiC,OAA0B;AACzE,QAAM,YAAY,oBAAI,IAAY;AAElC,WAAS,MAAM,SAAwB;AACrC,QAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,cAAQ,QAAQ,KAAK;AACrB;AAAA,IACF;AACA,QAAI,CAAC,WAAW,OAAO,YAAY,SAAU;AAC7C,eAAW,CAAC,KAAK,WAAW,KAAK,OAAO,QAAQ,OAAkC,GAAG;AACnF,UAAI,eAAe,GAAG,KAAK,OAAO,gBAAgB,YAAY,YAAY,SAAS,GAAG;AACpF,kBAAU,IAAI,WAAW;AAAA,MAC3B,OAAO;AACL,cAAM,WAAW;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,KAAK;AACX,SAAO,MAAM,KAAK,SAAS;AAC7B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
const defaultRunner = (command, args, cwd) => execFileSync(command, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
5
|
+
function tryRun(runner, command, args, cwd) {
|
|
6
|
+
try {
|
|
7
|
+
return runner(command, args, cwd);
|
|
8
|
+
} catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function normalizeGitRepository(remoteUrl) {
|
|
13
|
+
const trimmed = remoteUrl.trim().replace(/\.git$/, "");
|
|
14
|
+
const sshMatch = trimmed.match(/^git@github\.com:([^/]+\/[^/]+)$/);
|
|
15
|
+
if (sshMatch) return sshMatch[1] ?? null;
|
|
16
|
+
try {
|
|
17
|
+
const parsed = new URL(trimmed);
|
|
18
|
+
if (parsed.hostname !== "github.com") return null;
|
|
19
|
+
return parsed.pathname.replace(/^\/+/, "") || null;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function inspectGitSource(cwd, runner = defaultRunner) {
|
|
25
|
+
const branch = tryRun(runner, "git", ["branch", "--show-current"], cwd);
|
|
26
|
+
const remoteUrl = tryRun(runner, "git", ["remote", "get-url", "origin"], cwd);
|
|
27
|
+
if (!branch || !remoteUrl) return null;
|
|
28
|
+
const repo = normalizeGitRepository(remoteUrl);
|
|
29
|
+
if (!repo) return null;
|
|
30
|
+
const commitSha = tryRun(runner, "git", ["rev-parse", "HEAD"], cwd) ?? void 0;
|
|
31
|
+
return {
|
|
32
|
+
mode: "git",
|
|
33
|
+
reason: `GitHub remote origin and branch ${branch} detected`,
|
|
34
|
+
repo,
|
|
35
|
+
branch,
|
|
36
|
+
commitSha
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function assertGitDeployReady(cwd, runner = defaultRunner) {
|
|
40
|
+
const status = tryRun(runner, "git", ["status", "--porcelain"], cwd);
|
|
41
|
+
if (status === null) throw new Error("Git is required for --source git.");
|
|
42
|
+
if (status.length > 0) throw new Error("Git-backed deploy requires a clean working tree.");
|
|
43
|
+
const upstream = tryRun(runner, "git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], cwd);
|
|
44
|
+
if (!upstream) throw new Error("The current branch has no upstream. Push it before a Git-backed deploy.");
|
|
45
|
+
const ahead = tryRun(runner, "git", ["rev-list", "--count", "@{u}..HEAD"], cwd);
|
|
46
|
+
if (ahead === null || Number.parseInt(ahead, 10) > 0) {
|
|
47
|
+
throw new Error("The current branch has local commits that are not pushed to its upstream.");
|
|
48
|
+
}
|
|
49
|
+
const behind = tryRun(runner, "git", ["rev-list", "--count", "HEAD..@{u}"], cwd);
|
|
50
|
+
if (behind === null || Number.parseInt(behind, 10) > 0) {
|
|
51
|
+
throw new Error("The current branch is behind its upstream. Pull the latest commits before a Git-backed deploy.");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function isRailwayCliAvailable(cwd, runner = defaultRunner) {
|
|
55
|
+
return tryRun(runner, "railway", ["--version"], cwd) !== null;
|
|
56
|
+
}
|
|
57
|
+
function resolveRailwaySource(mode, cwd, runner = defaultRunner) {
|
|
58
|
+
const gitSource = inspectGitSource(cwd, runner);
|
|
59
|
+
if (mode === "git") {
|
|
60
|
+
if (!gitSource) {
|
|
61
|
+
throw new Error("--source git requires a GitHub origin remote and a current branch.");
|
|
62
|
+
}
|
|
63
|
+
assertGitDeployReady(cwd, runner);
|
|
64
|
+
return gitSource;
|
|
65
|
+
}
|
|
66
|
+
if (mode === "local") {
|
|
67
|
+
if (!isRailwayCliAvailable(cwd, runner)) {
|
|
68
|
+
throw new Error("--source local requires the Railway CLI. Install it from https://docs.railway.com/reference/cli-api.");
|
|
69
|
+
}
|
|
70
|
+
return { mode: "local", reason: "Local upload explicitly requested" };
|
|
71
|
+
}
|
|
72
|
+
if (gitSource) {
|
|
73
|
+
try {
|
|
74
|
+
assertGitDeployReady(cwd, runner);
|
|
75
|
+
return gitSource;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (!isRailwayCliAvailable(cwd, runner)) throw error;
|
|
78
|
+
const reason = error instanceof Error ? error.message : "Git source is not deployable";
|
|
79
|
+
return {
|
|
80
|
+
mode: "local",
|
|
81
|
+
reason: `${reason} Falling back to railway up.`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!isRailwayCliAvailable(cwd, runner)) {
|
|
86
|
+
throw new Error("No usable GitHub remote found and the Railway CLI is unavailable for local upload.");
|
|
87
|
+
}
|
|
88
|
+
return { mode: "local", reason: "No usable GitHub remote found; falling back to railway up" };
|
|
89
|
+
}
|
|
90
|
+
const REQUIRED_IGNORE_ENTRIES = [
|
|
91
|
+
{ label: ".env", matches: (line) => line === ".env" || line === ".env*" },
|
|
92
|
+
{ label: ".env.*", matches: (line) => line === ".env.*" || line === ".env*" },
|
|
93
|
+
{ label: "*.pem", matches: (line) => line === "*.pem" },
|
|
94
|
+
{ label: "*.key", matches: (line) => line === "*.key" },
|
|
95
|
+
{ label: "id_rsa", matches: (line) => line === "id_*" || line === "id_rsa" },
|
|
96
|
+
{ label: "id_ed25519", matches: (line) => line === "id_*" || line === "id_ed25519" },
|
|
97
|
+
{ label: ".git", matches: (line) => line === ".git" },
|
|
98
|
+
{ label: ".railway", matches: (line) => line === ".railway" },
|
|
99
|
+
{ label: "node_modules", matches: (line) => line === "node_modules" },
|
|
100
|
+
{ label: ".yarn/cache", matches: (line) => line === ".yarn/cache" },
|
|
101
|
+
{ label: ".next", matches: (line) => line === ".next" },
|
|
102
|
+
{ label: ".turbo", matches: (line) => line === ".turbo" },
|
|
103
|
+
{ label: "*.db", matches: (line) => line === "*.db" },
|
|
104
|
+
{ label: "*.sqlite", matches: (line) => line === "*.sqlite" || line === "*.sqlite*" },
|
|
105
|
+
{ label: "*.sqlite3", matches: (line) => line === "*.sqlite3" || line === "*.sqlite*" },
|
|
106
|
+
{ label: ".mercato/railway.json", matches: (line) => line === ".mercato/railway.json" || line === ".mercato/railway.json*" },
|
|
107
|
+
{ label: ".mercato/railway.json.local", matches: (line) => line === ".mercato/railway.json.local" || line === ".mercato/railway.json*" }
|
|
108
|
+
];
|
|
109
|
+
function assertLocalUploadSafe(cwd) {
|
|
110
|
+
const ignorePath = resolve(cwd, ".railwayignore");
|
|
111
|
+
if (!existsSync(ignorePath)) {
|
|
112
|
+
throw new Error("Local Railway upload requires a .railwayignore file.");
|
|
113
|
+
}
|
|
114
|
+
const entries = readFileSync(ignorePath, "utf8").split(/\r?\n/).map((line) => line.trim().replace(/^\/+|\/+$/g, "")).filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("!"));
|
|
115
|
+
const missing = REQUIRED_IGNORE_ENTRIES.filter((requirement) => !entries.some(requirement.matches)).map((requirement) => requirement.label);
|
|
116
|
+
if (missing.length > 0) {
|
|
117
|
+
throw new Error(`.railwayignore is missing required safety entries: ${missing.join(", ")}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export {
|
|
121
|
+
assertGitDeployReady,
|
|
122
|
+
assertLocalUploadSafe,
|
|
123
|
+
inspectGitSource,
|
|
124
|
+
isRailwayCliAvailable,
|
|
125
|
+
normalizeGitRepository,
|
|
126
|
+
resolveRailwaySource
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=source.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/lib/deploy/railway/source.ts"],
|
|
4
|
+
"sourcesContent": ["import { execFileSync } from 'node:child_process'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport type { RailwaySource, RailwaySourceMode } from './types'\n\ntype CommandRunner = (command: string, args: string[], cwd: string) => string\n\nconst defaultRunner: CommandRunner = (command, args, cwd) =>\n execFileSync(command, args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim()\n\nfunction tryRun(runner: CommandRunner, command: string, args: string[], cwd: string): string | null {\n try {\n return runner(command, args, cwd)\n } catch {\n return null\n }\n}\n\nexport function normalizeGitRepository(remoteUrl: string): string | null {\n const trimmed = remoteUrl.trim().replace(/\\.git$/, '')\n const sshMatch = trimmed.match(/^git@github\\.com:([^/]+\\/[^/]+)$/)\n if (sshMatch) return sshMatch[1] ?? null\n try {\n const parsed = new URL(trimmed)\n if (parsed.hostname !== 'github.com') return null\n return parsed.pathname.replace(/^\\/+/, '') || null\n } catch {\n return null\n }\n}\n\nexport function inspectGitSource(cwd: string, runner: CommandRunner = defaultRunner): RailwaySource | null {\n const branch = tryRun(runner, 'git', ['branch', '--show-current'], cwd)\n const remoteUrl = tryRun(runner, 'git', ['remote', 'get-url', 'origin'], cwd)\n if (!branch || !remoteUrl) return null\n const repo = normalizeGitRepository(remoteUrl)\n if (!repo) return null\n const commitSha = tryRun(runner, 'git', ['rev-parse', 'HEAD'], cwd) ?? undefined\n return {\n mode: 'git',\n reason: `GitHub remote origin and branch ${branch} detected`,\n repo,\n branch,\n commitSha,\n }\n}\n\nexport function assertGitDeployReady(cwd: string, runner: CommandRunner = defaultRunner): void {\n const status = tryRun(runner, 'git', ['status', '--porcelain'], cwd)\n if (status === null) throw new Error('Git is required for --source git.')\n if (status.length > 0) throw new Error('Git-backed deploy requires a clean working tree.')\n const upstream = tryRun(runner, 'git', ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'], cwd)\n if (!upstream) throw new Error('The current branch has no upstream. Push it before a Git-backed deploy.')\n const ahead = tryRun(runner, 'git', ['rev-list', '--count', '@{u}..HEAD'], cwd)\n if (ahead === null || Number.parseInt(ahead, 10) > 0) {\n throw new Error('The current branch has local commits that are not pushed to its upstream.')\n }\n const behind = tryRun(runner, 'git', ['rev-list', '--count', 'HEAD..@{u}'], cwd)\n if (behind === null || Number.parseInt(behind, 10) > 0) {\n throw new Error('The current branch is behind its upstream. Pull the latest commits before a Git-backed deploy.')\n }\n}\n\nexport function isRailwayCliAvailable(cwd: string, runner: CommandRunner = defaultRunner): boolean {\n return tryRun(runner, 'railway', ['--version'], cwd) !== null\n}\n\nexport function resolveRailwaySource(\n mode: RailwaySourceMode,\n cwd: string,\n runner: CommandRunner = defaultRunner,\n): RailwaySource {\n const gitSource = inspectGitSource(cwd, runner)\n if (mode === 'git') {\n if (!gitSource) {\n throw new Error('--source git requires a GitHub origin remote and a current branch.')\n }\n assertGitDeployReady(cwd, runner)\n return gitSource\n }\n if (mode === 'local') {\n if (!isRailwayCliAvailable(cwd, runner)) {\n throw new Error('--source local requires the Railway CLI. Install it from https://docs.railway.com/reference/cli-api.')\n }\n return { mode: 'local', reason: 'Local upload explicitly requested' }\n }\n if (gitSource) {\n try {\n assertGitDeployReady(cwd, runner)\n return gitSource\n } catch (error) {\n if (!isRailwayCliAvailable(cwd, runner)) throw error\n const reason = error instanceof Error ? error.message : 'Git source is not deployable'\n return {\n mode: 'local',\n reason: `${reason} Falling back to railway up.`,\n }\n }\n }\n if (!isRailwayCliAvailable(cwd, runner)) {\n throw new Error('No usable GitHub remote found and the Railway CLI is unavailable for local upload.')\n }\n return { mode: 'local', reason: 'No usable GitHub remote found; falling back to railway up' }\n}\n\nconst REQUIRED_IGNORE_ENTRIES = [\n { label: '.env', matches: (line: string) => line === '.env' || line === '.env*' },\n { label: '.env.*', matches: (line: string) => line === '.env.*' || line === '.env*' },\n { label: '*.pem', matches: (line: string) => line === '*.pem' },\n { label: '*.key', matches: (line: string) => line === '*.key' },\n { label: 'id_rsa', matches: (line: string) => line === 'id_*' || line === 'id_rsa' },\n { label: 'id_ed25519', matches: (line: string) => line === 'id_*' || line === 'id_ed25519' },\n { label: '.git', matches: (line: string) => line === '.git' },\n { label: '.railway', matches: (line: string) => line === '.railway' },\n { label: 'node_modules', matches: (line: string) => line === 'node_modules' },\n { label: '.yarn/cache', matches: (line: string) => line === '.yarn/cache' },\n { label: '.next', matches: (line: string) => line === '.next' },\n { label: '.turbo', matches: (line: string) => line === '.turbo' },\n { label: '*.db', matches: (line: string) => line === '*.db' },\n { label: '*.sqlite', matches: (line: string) => line === '*.sqlite' || line === '*.sqlite*' },\n { label: '*.sqlite3', matches: (line: string) => line === '*.sqlite3' || line === '*.sqlite*' },\n { label: '.mercato/railway.json', matches: (line: string) =>\n line === '.mercato/railway.json' || line === '.mercato/railway.json*' },\n { label: '.mercato/railway.json.local', matches: (line: string) =>\n line === '.mercato/railway.json.local' || line === '.mercato/railway.json*' },\n]\n\nexport function assertLocalUploadSafe(cwd: string): void {\n const ignorePath = resolve(cwd, '.railwayignore')\n if (!existsSync(ignorePath)) {\n throw new Error('Local Railway upload requires a .railwayignore file.')\n }\n const entries = readFileSync(ignorePath, 'utf8')\n .split(/\\r?\\n/)\n .map((line) => line.trim().replace(/^\\/+|\\/+$/g, ''))\n .filter((line) => line.length > 0 && !line.startsWith('#') && !line.startsWith('!'))\n const missing = REQUIRED_IGNORE_ENTRIES\n .filter((requirement) => !entries.some(requirement.matches))\n .map((requirement) => requirement.label)\n if (missing.length > 0) {\n throw new Error(`.railwayignore is missing required safety entries: ${missing.join(', ')}`)\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
function railwayStatePath(cwd, track) {
|
|
4
|
+
return resolve(cwd, ".mercato", track ? "railway.json" : "railway.json.local");
|
|
5
|
+
}
|
|
6
|
+
function isRailwayState(value) {
|
|
7
|
+
if (!value || typeof value !== "object") return false;
|
|
8
|
+
const record = value;
|
|
9
|
+
return record.schemaVersion === 1 && record.provider === "railway" && typeof record.projectName === "string" && typeof record.environments === "object" && record.environments !== null;
|
|
10
|
+
}
|
|
11
|
+
function loadRailwayState(path) {
|
|
12
|
+
if (!existsSync(path)) return null;
|
|
13
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
14
|
+
if (!isRailwayState(parsed)) {
|
|
15
|
+
throw new Error(`Unsupported or invalid Railway state file: ${path}`);
|
|
16
|
+
}
|
|
17
|
+
return parsed;
|
|
18
|
+
}
|
|
19
|
+
function createRailwayState(projectName, cliVersion) {
|
|
20
|
+
return {
|
|
21
|
+
schemaVersion: 1,
|
|
22
|
+
provider: "railway",
|
|
23
|
+
projectName,
|
|
24
|
+
environments: {},
|
|
25
|
+
writtenBy: { cliVersion }
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function saveRailwayState(path, state) {
|
|
29
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
30
|
+
const temporaryPath = `${path}.tmp`;
|
|
31
|
+
writeFileSync(temporaryPath, `${JSON.stringify(state, null, 2)}
|
|
32
|
+
`, { mode: 384 });
|
|
33
|
+
renameSync(temporaryPath, path);
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
createRailwayState,
|
|
37
|
+
loadRailwayState,
|
|
38
|
+
railwayStatePath,
|
|
39
|
+
saveRailwayState
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/lib/deploy/railway/state.ts"],
|
|
4
|
+
"sourcesContent": ["import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport type { RailwayState } from './types'\n\nexport function railwayStatePath(cwd: string, track: boolean): string {\n return resolve(cwd, '.mercato', track ? 'railway.json' : 'railway.json.local')\n}\n\nfunction isRailwayState(value: unknown): value is RailwayState {\n if (!value || typeof value !== 'object') return false\n const record = value as Record<string, unknown>\n return (\n record.schemaVersion === 1 &&\n record.provider === 'railway' &&\n typeof record.projectName === 'string' &&\n typeof record.environments === 'object' &&\n record.environments !== null\n )\n}\n\nexport function loadRailwayState(path: string): RailwayState | null {\n if (!existsSync(path)) return null\n const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'))\n if (!isRailwayState(parsed)) {\n throw new Error(`Unsupported or invalid Railway state file: ${path}`)\n }\n return parsed\n}\n\nexport function createRailwayState(projectName: string, cliVersion: string): RailwayState {\n return {\n schemaVersion: 1,\n provider: 'railway',\n projectName,\n environments: {},\n writtenBy: { cliVersion },\n }\n}\n\nexport function saveRailwayState(path: string, state: RailwayState): void {\n mkdirSync(dirname(path), { recursive: true })\n const temporaryPath = `${path}.tmp`\n writeFileSync(temporaryPath, `${JSON.stringify(state, null, 2)}\\n`, { mode: 0o600 })\n renameSync(temporaryPath, path)\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,YAAY,WAAW,cAAc,YAAY,qBAAqB;AAC/E,SAAS,SAAS,eAAe;AAG1B,SAAS,iBAAiB,KAAa,OAAwB;AACpE,SAAO,QAAQ,KAAK,YAAY,QAAQ,iBAAiB,oBAAoB;AAC/E;AAEA,SAAS,eAAe,OAAuC;AAC7D,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,SAAS;AACf,SACE,OAAO,kBAAkB,KACzB,OAAO,aAAa,aACpB,OAAO,OAAO,gBAAgB,YAC9B,OAAO,OAAO,iBAAiB,YAC/B,OAAO,iBAAiB;AAE5B;AAEO,SAAS,iBAAiB,MAAmC;AAClE,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,QAAM,SAAkB,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AAC7D,MAAI,CAAC,eAAe,MAAM,GAAG;AAC3B,UAAM,IAAI,MAAM,8CAA8C,IAAI,EAAE;AAAA,EACtE;AACA,SAAO;AACT;AAEO,SAAS,mBAAmB,aAAqB,YAAkC;AACxF,SAAO;AAAA,IACL,eAAe;AAAA,IACf,UAAU;AAAA,IACV;AAAA,IACA,cAAc,CAAC;AAAA,IACf,WAAW,EAAE,WAAW;AAAA,EAC1B;AACF;AAEO,SAAS,iBAAiB,MAAc,OAA2B;AACxE,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,QAAM,gBAAgB,GAAG,IAAI;AAC7B,gBAAc,eAAe,GAAG,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAAA,GAAM,EAAE,MAAM,IAAM,CAAC;AACnF,aAAW,eAAe,IAAI;AAChC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
function railwayTokenConfigPath(environment = process.env, platform = process.platform) {
|
|
5
|
+
if (platform === "win32") {
|
|
6
|
+
return join(environment.APPDATA || join(homedir(), "AppData", "Roaming"), "open-mercato", "railway.json");
|
|
7
|
+
}
|
|
8
|
+
return join(environment.XDG_CONFIG_HOME || join(homedir(), ".config"), "open-mercato", "railway.json");
|
|
9
|
+
}
|
|
10
|
+
function readCachedRailwayToken(path, platform = process.platform) {
|
|
11
|
+
if (!existsSync(path)) return null;
|
|
12
|
+
if (platform !== "win32") {
|
|
13
|
+
const mode = statSync(path).mode & 511;
|
|
14
|
+
if ((mode & 63) !== 0) {
|
|
15
|
+
throw new Error(`Refusing to read ${path}: permissions must be 0600.`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
19
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
20
|
+
const token = parsed.token;
|
|
21
|
+
return typeof token === "string" && token.trim() ? token.trim() : null;
|
|
22
|
+
}
|
|
23
|
+
function writeCachedRailwayToken(path, token) {
|
|
24
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
25
|
+
writeFileSync(path, `${JSON.stringify({ token }, null, 2)}
|
|
26
|
+
`, { mode: 384 });
|
|
27
|
+
if (process.platform !== "win32") chmodSync(path, 384);
|
|
28
|
+
}
|
|
29
|
+
function resolveRailwayToken(input) {
|
|
30
|
+
const environment = input.environment ?? process.env;
|
|
31
|
+
if (input.flagToken?.trim()) return { token: input.flagToken.trim(), source: "flag" };
|
|
32
|
+
if (environment.RAILWAY_API_TOKEN?.trim()) {
|
|
33
|
+
return { token: environment.RAILWAY_API_TOKEN.trim(), source: "environment" };
|
|
34
|
+
}
|
|
35
|
+
const configPath = input.configPath ?? railwayTokenConfigPath(environment);
|
|
36
|
+
const cached = readCachedRailwayToken(configPath);
|
|
37
|
+
if (cached) return { token: cached, source: "cache" };
|
|
38
|
+
return { token: null, source: null };
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
railwayTokenConfigPath,
|
|
42
|
+
readCachedRailwayToken,
|
|
43
|
+
resolveRailwayToken,
|
|
44
|
+
writeCachedRailwayToken
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=token.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/lib/deploy/railway/token.ts"],
|
|
4
|
+
"sourcesContent": ["import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\n\nexport function railwayTokenConfigPath(\n environment: NodeJS.ProcessEnv = process.env,\n platform: NodeJS.Platform = process.platform,\n): string {\n if (platform === 'win32') {\n return join(environment.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'open-mercato', 'railway.json')\n }\n return join(environment.XDG_CONFIG_HOME || join(homedir(), '.config'), 'open-mercato', 'railway.json')\n}\n\nexport function readCachedRailwayToken(path: string, platform: NodeJS.Platform = process.platform): string | null {\n if (!existsSync(path)) return null\n if (platform !== 'win32') {\n const mode = statSync(path).mode & 0o777\n if ((mode & 0o077) !== 0) {\n throw new Error(`Refusing to read ${path}: permissions must be 0600.`)\n }\n }\n const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'))\n if (!parsed || typeof parsed !== 'object') return null\n const token = (parsed as Record<string, unknown>).token\n return typeof token === 'string' && token.trim() ? token.trim() : null\n}\n\nexport function writeCachedRailwayToken(path: string, token: string): void {\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, `${JSON.stringify({ token }, null, 2)}\\n`, { mode: 0o600 })\n if (process.platform !== 'win32') chmodSync(path, 0o600)\n}\n\nexport function resolveRailwayToken(input: {\n flagToken?: string\n environment?: NodeJS.ProcessEnv\n configPath?: string\n}): { token: string | null; source: 'flag' | 'environment' | 'cache' | null } {\n const environment = input.environment ?? process.env\n if (input.flagToken?.trim()) return { token: input.flagToken.trim(), source: 'flag' }\n if (environment.RAILWAY_API_TOKEN?.trim()) {\n return { token: environment.RAILWAY_API_TOKEN.trim(), source: 'environment' }\n }\n const configPath = input.configPath ?? railwayTokenConfigPath(environment)\n const cached = readCachedRailwayToken(configPath)\n if (cached) return { token: cached, source: 'cache' }\n return { token: null, source: null }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,WAAW,YAAY,WAAW,cAAc,UAAU,qBAAqB;AACxF,SAAS,eAAe;AACxB,SAAS,SAAS,YAAY;AAEvB,SAAS,uBACd,cAAiC,QAAQ,KACzC,WAA4B,QAAQ,UAC5B;AACR,MAAI,aAAa,SAAS;AACxB,WAAO,KAAK,YAAY,WAAW,KAAK,QAAQ,GAAG,WAAW,SAAS,GAAG,gBAAgB,cAAc;AAAA,EAC1G;AACA,SAAO,KAAK,YAAY,mBAAmB,KAAK,QAAQ,GAAG,SAAS,GAAG,gBAAgB,cAAc;AACvG;AAEO,SAAS,uBAAuB,MAAc,WAA4B,QAAQ,UAAyB;AAChH,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI,aAAa,SAAS;AACxB,UAAM,OAAO,SAAS,IAAI,EAAE,OAAO;AACnC,SAAK,OAAO,QAAW,GAAG;AACxB,YAAM,IAAI,MAAM,oBAAoB,IAAI,6BAA6B;AAAA,IACvE;AAAA,EACF;AACA,QAAM,SAAkB,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AAC7D,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAM,QAAS,OAAmC;AAClD,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,IAAI,MAAM,KAAK,IAAI;AACpE;AAEO,SAAS,wBAAwB,MAAc,OAAqB;AACzE,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,gBAAc,MAAM,GAAG,KAAK,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAAA,GAAM,EAAE,MAAM,IAAM,CAAC;AAC9E,MAAI,QAAQ,aAAa,QAAS,WAAU,MAAM,GAAK;AACzD;AAEO,SAAS,oBAAoB,OAI0C;AAC5E,QAAM,cAAc,MAAM,eAAe,QAAQ;AACjD,MAAI,MAAM,WAAW,KAAK,EAAG,QAAO,EAAE,OAAO,MAAM,UAAU,KAAK,GAAG,QAAQ,OAAO;AACpF,MAAI,YAAY,mBAAmB,KAAK,GAAG;AACzC,WAAO,EAAE,OAAO,YAAY,kBAAkB,KAAK,GAAG,QAAQ,cAAc;AAAA,EAC9E;AACA,QAAM,aAAa,MAAM,cAAc,uBAAuB,WAAW;AACzE,QAAM,SAAS,uBAAuB,UAAU;AAChD,MAAI,OAAQ,QAAO,EAAE,OAAO,QAAQ,QAAQ,QAAQ;AACpD,SAAO,EAAE,OAAO,MAAM,QAAQ,KAAK;AACrC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=types.js.map
|
package/dist/mercato.js
CHANGED
|
@@ -60,7 +60,7 @@ const TURBOPACK_CORRUPTION_PATTERNS = [
|
|
|
60
60
|
"Unable to open static sorted file",
|
|
61
61
|
"TurbopackInternalError"
|
|
62
62
|
];
|
|
63
|
-
const BUILTIN_CLI_MODULE_IDS = /* @__PURE__ */ new Set(["queue", "generate", "db", "server", "test"]);
|
|
63
|
+
const BUILTIN_CLI_MODULE_IDS = /* @__PURE__ */ new Set(["queue", "generate", "deploy", "db", "server", "test"]);
|
|
64
64
|
function collectNestedErrors(error, seen = /* @__PURE__ */ new Set()) {
|
|
65
65
|
if (!error || seen.has(error)) {
|
|
66
66
|
return [];
|
|
@@ -192,16 +192,16 @@ function isTurbopackCacheCorruption(output) {
|
|
|
192
192
|
function removeTurbopackDevCache(appDir) {
|
|
193
193
|
fs.rmSync(path.join(appDir, ".mercato", "next", "dev"), { recursive: true, force: true });
|
|
194
194
|
}
|
|
195
|
-
async function ensureEnvLoaded() {
|
|
195
|
+
async function ensureEnvLoaded(options = {}) {
|
|
196
196
|
if (envLoaded) return;
|
|
197
197
|
envLoaded = true;
|
|
198
|
-
const quietDotenv = process.env.DOTENV_CONFIG_QUIET === "1" || process.env.DOTENV_CONFIG_QUIET === "true";
|
|
198
|
+
const quietDotenv = options.quiet === true || process.env.DOTENV_CONFIG_QUIET === "1" || process.env.DOTENV_CONFIG_QUIET === "true";
|
|
199
199
|
try {
|
|
200
200
|
const { createResolver } = await import("./lib/resolver.js");
|
|
201
201
|
const resolver = createResolver();
|
|
202
202
|
const appDir = resolver.getAppDir();
|
|
203
203
|
const envPath = path.join(appDir, ".env");
|
|
204
|
-
if (!fs.existsSync(envPath) && process.env.NODE_ENV !== "production") {
|
|
204
|
+
if (options.createIfMissing !== false && !fs.existsSync(envPath) && process.env.NODE_ENV !== "production") {
|
|
205
205
|
const examplePath = path.join(appDir, ".env.example");
|
|
206
206
|
if (fs.existsSync(examplePath)) {
|
|
207
207
|
fs.copyFileSync(examplePath, envPath);
|
|
@@ -568,9 +568,9 @@ async function buildAllModules() {
|
|
|
568
568
|
return all;
|
|
569
569
|
}
|
|
570
570
|
async function run(argv = process.argv) {
|
|
571
|
-
await ensureEnvLoaded();
|
|
572
571
|
const [, , ...parts] = argv;
|
|
573
572
|
const [first, second, ...remaining] = parts;
|
|
573
|
+
await ensureEnvLoaded({ createIfMissing: first !== "deploy", quiet: first === "deploy" });
|
|
574
574
|
if (first === "init") {
|
|
575
575
|
const { execSync } = await import("child_process");
|
|
576
576
|
console.log("\u{1F680} Initializing Open Mercato app...\n");
|
|
@@ -1184,6 +1184,18 @@ async function run(argv = process.argv) {
|
|
|
1184
1184
|
}
|
|
1185
1185
|
}
|
|
1186
1186
|
const all = modules.slice();
|
|
1187
|
+
all.push({
|
|
1188
|
+
id: "deploy",
|
|
1189
|
+
cli: [
|
|
1190
|
+
{
|
|
1191
|
+
command: "railway",
|
|
1192
|
+
run: async (args) => {
|
|
1193
|
+
const { runRailwayDeploy } = await import("./lib/deploy/railway/index.js");
|
|
1194
|
+
await runRailwayDeploy(args);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
]
|
|
1198
|
+
});
|
|
1187
1199
|
all.push({
|
|
1188
1200
|
id: "queue",
|
|
1189
1201
|
cli: [
|
|
@@ -1976,11 +1988,14 @@ async function run(argv = process.argv) {
|
|
|
1976
1988
|
}
|
|
1977
1989
|
console.log("");
|
|
1978
1990
|
const started = Date.now();
|
|
1979
|
-
|
|
1991
|
+
const loggedArgs = modName === "deploy" && cmdName === "railway" ? (await import("./lib/deploy/railway/options.js")).redactRailwayCliArgs(rest) : rest;
|
|
1992
|
+
console.log(`\u{1F680} Running ${modName}:${cmdName} ${loggedArgs.join(" ")}`);
|
|
1980
1993
|
try {
|
|
1981
1994
|
await cmd.run(rest);
|
|
1982
|
-
|
|
1983
|
-
|
|
1995
|
+
if (modName !== "deploy" || cmdName !== "railway") {
|
|
1996
|
+
const ms = Date.now() - started;
|
|
1997
|
+
console.log(`\u23F1\uFE0F Done in ${ms}ms`);
|
|
1998
|
+
}
|
|
1984
1999
|
return 0;
|
|
1985
2000
|
} catch (e) {
|
|
1986
2001
|
console.error(`\u{1F4A5} Failed: ${formatCliFailureMessage(modName, cmdName, e)}`);
|