@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,143 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
3
|
+
import { resolve } from 'node:path'
|
|
4
|
+
import type { RailwaySource, RailwaySourceMode } from './types'
|
|
5
|
+
|
|
6
|
+
type CommandRunner = (command: string, args: string[], cwd: string) => string
|
|
7
|
+
|
|
8
|
+
const defaultRunner: CommandRunner = (command, args, cwd) =>
|
|
9
|
+
execFileSync(command, args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim()
|
|
10
|
+
|
|
11
|
+
function tryRun(runner: CommandRunner, command: string, args: string[], cwd: string): string | null {
|
|
12
|
+
try {
|
|
13
|
+
return runner(command, args, cwd)
|
|
14
|
+
} catch {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function normalizeGitRepository(remoteUrl: string): string | null {
|
|
20
|
+
const trimmed = remoteUrl.trim().replace(/\.git$/, '')
|
|
21
|
+
const sshMatch = trimmed.match(/^git@github\.com:([^/]+\/[^/]+)$/)
|
|
22
|
+
if (sshMatch) return sshMatch[1] ?? null
|
|
23
|
+
try {
|
|
24
|
+
const parsed = new URL(trimmed)
|
|
25
|
+
if (parsed.hostname !== 'github.com') return null
|
|
26
|
+
return parsed.pathname.replace(/^\/+/, '') || null
|
|
27
|
+
} catch {
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function inspectGitSource(cwd: string, runner: CommandRunner = defaultRunner): RailwaySource | null {
|
|
33
|
+
const branch = tryRun(runner, 'git', ['branch', '--show-current'], cwd)
|
|
34
|
+
const remoteUrl = tryRun(runner, 'git', ['remote', 'get-url', 'origin'], cwd)
|
|
35
|
+
if (!branch || !remoteUrl) return null
|
|
36
|
+
const repo = normalizeGitRepository(remoteUrl)
|
|
37
|
+
if (!repo) return null
|
|
38
|
+
const commitSha = tryRun(runner, 'git', ['rev-parse', 'HEAD'], cwd) ?? undefined
|
|
39
|
+
return {
|
|
40
|
+
mode: 'git',
|
|
41
|
+
reason: `GitHub remote origin and branch ${branch} detected`,
|
|
42
|
+
repo,
|
|
43
|
+
branch,
|
|
44
|
+
commitSha,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function assertGitDeployReady(cwd: string, runner: CommandRunner = defaultRunner): void {
|
|
49
|
+
const status = tryRun(runner, 'git', ['status', '--porcelain'], cwd)
|
|
50
|
+
if (status === null) throw new Error('Git is required for --source git.')
|
|
51
|
+
if (status.length > 0) throw new Error('Git-backed deploy requires a clean working tree.')
|
|
52
|
+
const upstream = tryRun(runner, 'git', ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'], cwd)
|
|
53
|
+
if (!upstream) throw new Error('The current branch has no upstream. Push it before a Git-backed deploy.')
|
|
54
|
+
const ahead = tryRun(runner, 'git', ['rev-list', '--count', '@{u}..HEAD'], cwd)
|
|
55
|
+
if (ahead === null || Number.parseInt(ahead, 10) > 0) {
|
|
56
|
+
throw new Error('The current branch has local commits that are not pushed to its upstream.')
|
|
57
|
+
}
|
|
58
|
+
const behind = tryRun(runner, 'git', ['rev-list', '--count', 'HEAD..@{u}'], cwd)
|
|
59
|
+
if (behind === null || Number.parseInt(behind, 10) > 0) {
|
|
60
|
+
throw new Error('The current branch is behind its upstream. Pull the latest commits before a Git-backed deploy.')
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isRailwayCliAvailable(cwd: string, runner: CommandRunner = defaultRunner): boolean {
|
|
65
|
+
return tryRun(runner, 'railway', ['--version'], cwd) !== null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function resolveRailwaySource(
|
|
69
|
+
mode: RailwaySourceMode,
|
|
70
|
+
cwd: string,
|
|
71
|
+
runner: CommandRunner = defaultRunner,
|
|
72
|
+
): RailwaySource {
|
|
73
|
+
const gitSource = inspectGitSource(cwd, runner)
|
|
74
|
+
if (mode === 'git') {
|
|
75
|
+
if (!gitSource) {
|
|
76
|
+
throw new Error('--source git requires a GitHub origin remote and a current branch.')
|
|
77
|
+
}
|
|
78
|
+
assertGitDeployReady(cwd, runner)
|
|
79
|
+
return gitSource
|
|
80
|
+
}
|
|
81
|
+
if (mode === 'local') {
|
|
82
|
+
if (!isRailwayCliAvailable(cwd, runner)) {
|
|
83
|
+
throw new Error('--source local requires the Railway CLI. Install it from https://docs.railway.com/reference/cli-api.')
|
|
84
|
+
}
|
|
85
|
+
return { mode: 'local', reason: 'Local upload explicitly requested' }
|
|
86
|
+
}
|
|
87
|
+
if (gitSource) {
|
|
88
|
+
try {
|
|
89
|
+
assertGitDeployReady(cwd, runner)
|
|
90
|
+
return gitSource
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (!isRailwayCliAvailable(cwd, runner)) throw error
|
|
93
|
+
const reason = error instanceof Error ? error.message : 'Git source is not deployable'
|
|
94
|
+
return {
|
|
95
|
+
mode: 'local',
|
|
96
|
+
reason: `${reason} Falling back to railway up.`,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!isRailwayCliAvailable(cwd, runner)) {
|
|
101
|
+
throw new Error('No usable GitHub remote found and the Railway CLI is unavailable for local upload.')
|
|
102
|
+
}
|
|
103
|
+
return { mode: 'local', reason: 'No usable GitHub remote found; falling back to railway up' }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const REQUIRED_IGNORE_ENTRIES = [
|
|
107
|
+
{ label: '.env', matches: (line: string) => line === '.env' || line === '.env*' },
|
|
108
|
+
{ label: '.env.*', matches: (line: string) => line === '.env.*' || line === '.env*' },
|
|
109
|
+
{ label: '*.pem', matches: (line: string) => line === '*.pem' },
|
|
110
|
+
{ label: '*.key', matches: (line: string) => line === '*.key' },
|
|
111
|
+
{ label: 'id_rsa', matches: (line: string) => line === 'id_*' || line === 'id_rsa' },
|
|
112
|
+
{ label: 'id_ed25519', matches: (line: string) => line === 'id_*' || line === 'id_ed25519' },
|
|
113
|
+
{ label: '.git', matches: (line: string) => line === '.git' },
|
|
114
|
+
{ label: '.railway', matches: (line: string) => line === '.railway' },
|
|
115
|
+
{ label: 'node_modules', matches: (line: string) => line === 'node_modules' },
|
|
116
|
+
{ label: '.yarn/cache', matches: (line: string) => line === '.yarn/cache' },
|
|
117
|
+
{ label: '.next', matches: (line: string) => line === '.next' },
|
|
118
|
+
{ label: '.turbo', matches: (line: string) => line === '.turbo' },
|
|
119
|
+
{ label: '*.db', matches: (line: string) => line === '*.db' },
|
|
120
|
+
{ label: '*.sqlite', matches: (line: string) => line === '*.sqlite' || line === '*.sqlite*' },
|
|
121
|
+
{ label: '*.sqlite3', matches: (line: string) => line === '*.sqlite3' || line === '*.sqlite*' },
|
|
122
|
+
{ label: '.mercato/railway.json', matches: (line: string) =>
|
|
123
|
+
line === '.mercato/railway.json' || line === '.mercato/railway.json*' },
|
|
124
|
+
{ label: '.mercato/railway.json.local', matches: (line: string) =>
|
|
125
|
+
line === '.mercato/railway.json.local' || line === '.mercato/railway.json*' },
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
export function assertLocalUploadSafe(cwd: string): void {
|
|
129
|
+
const ignorePath = resolve(cwd, '.railwayignore')
|
|
130
|
+
if (!existsSync(ignorePath)) {
|
|
131
|
+
throw new Error('Local Railway upload requires a .railwayignore file.')
|
|
132
|
+
}
|
|
133
|
+
const entries = readFileSync(ignorePath, 'utf8')
|
|
134
|
+
.split(/\r?\n/)
|
|
135
|
+
.map((line) => line.trim().replace(/^\/+|\/+$/g, ''))
|
|
136
|
+
.filter((line) => line.length > 0 && !line.startsWith('#') && !line.startsWith('!'))
|
|
137
|
+
const missing = REQUIRED_IGNORE_ENTRIES
|
|
138
|
+
.filter((requirement) => !entries.some(requirement.matches))
|
|
139
|
+
.map((requirement) => requirement.label)
|
|
140
|
+
if (missing.length > 0) {
|
|
141
|
+
throw new Error(`.railwayignore is missing required safety entries: ${missing.join(', ')}`)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, resolve } from 'node:path'
|
|
3
|
+
import type { RailwayState } from './types'
|
|
4
|
+
|
|
5
|
+
export function railwayStatePath(cwd: string, track: boolean): string {
|
|
6
|
+
return resolve(cwd, '.mercato', track ? 'railway.json' : 'railway.json.local')
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isRailwayState(value: unknown): value is RailwayState {
|
|
10
|
+
if (!value || typeof value !== 'object') return false
|
|
11
|
+
const record = value as Record<string, unknown>
|
|
12
|
+
return (
|
|
13
|
+
record.schemaVersion === 1 &&
|
|
14
|
+
record.provider === 'railway' &&
|
|
15
|
+
typeof record.projectName === 'string' &&
|
|
16
|
+
typeof record.environments === 'object' &&
|
|
17
|
+
record.environments !== null
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function loadRailwayState(path: string): RailwayState | null {
|
|
22
|
+
if (!existsSync(path)) return null
|
|
23
|
+
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'))
|
|
24
|
+
if (!isRailwayState(parsed)) {
|
|
25
|
+
throw new Error(`Unsupported or invalid Railway state file: ${path}`)
|
|
26
|
+
}
|
|
27
|
+
return parsed
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createRailwayState(projectName: string, cliVersion: string): RailwayState {
|
|
31
|
+
return {
|
|
32
|
+
schemaVersion: 1,
|
|
33
|
+
provider: 'railway',
|
|
34
|
+
projectName,
|
|
35
|
+
environments: {},
|
|
36
|
+
writtenBy: { cliVersion },
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function saveRailwayState(path: string, state: RailwayState): void {
|
|
41
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
42
|
+
const temporaryPath = `${path}.tmp`
|
|
43
|
+
writeFileSync(temporaryPath, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 })
|
|
44
|
+
renameSync(temporaryPath, path)
|
|
45
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
|
|
5
|
+
export function railwayTokenConfigPath(
|
|
6
|
+
environment: NodeJS.ProcessEnv = process.env,
|
|
7
|
+
platform: NodeJS.Platform = process.platform,
|
|
8
|
+
): string {
|
|
9
|
+
if (platform === 'win32') {
|
|
10
|
+
return join(environment.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'open-mercato', 'railway.json')
|
|
11
|
+
}
|
|
12
|
+
return join(environment.XDG_CONFIG_HOME || join(homedir(), '.config'), 'open-mercato', 'railway.json')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function readCachedRailwayToken(path: string, platform: NodeJS.Platform = process.platform): string | null {
|
|
16
|
+
if (!existsSync(path)) return null
|
|
17
|
+
if (platform !== 'win32') {
|
|
18
|
+
const mode = statSync(path).mode & 0o777
|
|
19
|
+
if ((mode & 0o077) !== 0) {
|
|
20
|
+
throw new Error(`Refusing to read ${path}: permissions must be 0600.`)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'))
|
|
24
|
+
if (!parsed || typeof parsed !== 'object') return null
|
|
25
|
+
const token = (parsed as Record<string, unknown>).token
|
|
26
|
+
return typeof token === 'string' && token.trim() ? token.trim() : null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function writeCachedRailwayToken(path: string, token: string): void {
|
|
30
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
31
|
+
writeFileSync(path, `${JSON.stringify({ token }, null, 2)}\n`, { mode: 0o600 })
|
|
32
|
+
if (process.platform !== 'win32') chmodSync(path, 0o600)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function resolveRailwayToken(input: {
|
|
36
|
+
flagToken?: string
|
|
37
|
+
environment?: NodeJS.ProcessEnv
|
|
38
|
+
configPath?: string
|
|
39
|
+
}): { token: string | null; source: 'flag' | 'environment' | 'cache' | null } {
|
|
40
|
+
const environment = input.environment ?? process.env
|
|
41
|
+
if (input.flagToken?.trim()) return { token: input.flagToken.trim(), source: 'flag' }
|
|
42
|
+
if (environment.RAILWAY_API_TOKEN?.trim()) {
|
|
43
|
+
return { token: environment.RAILWAY_API_TOKEN.trim(), source: 'environment' }
|
|
44
|
+
}
|
|
45
|
+
const configPath = input.configPath ?? railwayTokenConfigPath(environment)
|
|
46
|
+
const cached = readCachedRailwayToken(configPath)
|
|
47
|
+
if (cached) return { token: cached, source: 'cache' }
|
|
48
|
+
return { token: null, source: null }
|
|
49
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export type RailwaySourceMode = 'auto' | 'git' | 'local'
|
|
2
|
+
export type ResolvedRailwaySourceMode = Exclude<RailwaySourceMode, 'auto'>
|
|
3
|
+
|
|
4
|
+
export type RailwayDeployOptions = {
|
|
5
|
+
project?: string
|
|
6
|
+
environment: string
|
|
7
|
+
service: string
|
|
8
|
+
worker: boolean
|
|
9
|
+
source: RailwaySourceMode
|
|
10
|
+
region?: string
|
|
11
|
+
envFile?: string
|
|
12
|
+
domain?: string
|
|
13
|
+
waitDomain: boolean
|
|
14
|
+
volume?: string
|
|
15
|
+
token?: string
|
|
16
|
+
nonInteractive: boolean
|
|
17
|
+
dryRun: boolean
|
|
18
|
+
cleanup: boolean
|
|
19
|
+
yes: boolean
|
|
20
|
+
writeEnv: boolean
|
|
21
|
+
track: boolean
|
|
22
|
+
forceRename: boolean
|
|
23
|
+
timeoutSeconds: number
|
|
24
|
+
allowedSecretKeys: string[]
|
|
25
|
+
verbose: boolean
|
|
26
|
+
help: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type RailwaySource = {
|
|
30
|
+
mode: ResolvedRailwaySourceMode
|
|
31
|
+
reason: string
|
|
32
|
+
repo?: string
|
|
33
|
+
branch?: string
|
|
34
|
+
commitSha?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type RailwayEnvironmentState = {
|
|
38
|
+
environmentId?: string
|
|
39
|
+
appServiceId?: string
|
|
40
|
+
workerServiceId?: string
|
|
41
|
+
postgresServiceId?: string
|
|
42
|
+
redisServiceId?: string
|
|
43
|
+
domainId?: string
|
|
44
|
+
volumeId?: string
|
|
45
|
+
appUrl?: string
|
|
46
|
+
source?: RailwaySource
|
|
47
|
+
lastDeployIds?: {
|
|
48
|
+
app?: string
|
|
49
|
+
worker?: string
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type RailwayState = {
|
|
54
|
+
schemaVersion: 1
|
|
55
|
+
provider: 'railway'
|
|
56
|
+
projectId?: string
|
|
57
|
+
workspaceId?: string
|
|
58
|
+
projectName: string
|
|
59
|
+
environments: Record<string, RailwayEnvironmentState>
|
|
60
|
+
writtenBy: {
|
|
61
|
+
cliVersion: string
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type RailwayGraphqlOperation = {
|
|
66
|
+
name: string
|
|
67
|
+
query: string
|
|
68
|
+
mutation?: boolean
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type RailwayGraphqlClient = {
|
|
72
|
+
request<T>(operation: RailwayGraphqlOperation, variables?: Record<string, unknown>): Promise<T>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type RailwayService = {
|
|
76
|
+
id: string
|
|
77
|
+
name: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type RailwayDeployment = {
|
|
81
|
+
id: string
|
|
82
|
+
status: string
|
|
83
|
+
staticUrl?: string | null
|
|
84
|
+
url?: string | null
|
|
85
|
+
serviceId?: string | null
|
|
86
|
+
environmentId?: string | null
|
|
87
|
+
}
|
package/src/mercato.ts
CHANGED
|
@@ -83,7 +83,7 @@ const TURBOPACK_CORRUPTION_PATTERNS = [
|
|
|
83
83
|
'TurbopackInternalError',
|
|
84
84
|
]
|
|
85
85
|
|
|
86
|
-
const BUILTIN_CLI_MODULE_IDS = new Set(['queue', 'generate', 'db', 'server', 'test'])
|
|
86
|
+
const BUILTIN_CLI_MODULE_IDS = new Set(['queue', 'generate', 'deploy', 'db', 'server', 'test'])
|
|
87
87
|
|
|
88
88
|
function collectNestedErrors(error: unknown, seen = new Set<unknown>()): ErrorWithCause[] {
|
|
89
89
|
if (!error || seen.has(error)) {
|
|
@@ -254,10 +254,13 @@ function removeTurbopackDevCache(appDir: string): void {
|
|
|
254
254
|
fs.rmSync(path.join(appDir, '.mercato', 'next', 'dev'), { recursive: true, force: true })
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
async function ensureEnvLoaded() {
|
|
257
|
+
async function ensureEnvLoaded(options: { createIfMissing?: boolean; quiet?: boolean } = {}) {
|
|
258
258
|
if (envLoaded) return
|
|
259
259
|
envLoaded = true
|
|
260
|
-
const quietDotenv =
|
|
260
|
+
const quietDotenv =
|
|
261
|
+
options.quiet === true ||
|
|
262
|
+
process.env.DOTENV_CONFIG_QUIET === '1' ||
|
|
263
|
+
process.env.DOTENV_CONFIG_QUIET === 'true'
|
|
261
264
|
|
|
262
265
|
// Try to find and load .env from the app directory
|
|
263
266
|
// First, try to find the app directory via resolver
|
|
@@ -268,7 +271,11 @@ async function ensureEnvLoaded() {
|
|
|
268
271
|
|
|
269
272
|
// Load .env from app directory if it exists
|
|
270
273
|
const envPath = path.join(appDir, '.env')
|
|
271
|
-
if (
|
|
274
|
+
if (
|
|
275
|
+
options.createIfMissing !== false &&
|
|
276
|
+
!fs.existsSync(envPath) &&
|
|
277
|
+
process.env.NODE_ENV !== 'production'
|
|
278
|
+
) {
|
|
272
279
|
const examplePath = path.join(appDir, '.env.example')
|
|
273
280
|
if (fs.existsSync(examplePath)) {
|
|
274
281
|
fs.copyFileSync(examplePath, envPath)
|
|
@@ -751,9 +758,9 @@ async function buildAllModules(): Promise<Module[]> {
|
|
|
751
758
|
}
|
|
752
759
|
|
|
753
760
|
export async function run(argv = process.argv) {
|
|
754
|
-
await ensureEnvLoaded()
|
|
755
761
|
const [, , ...parts] = argv
|
|
756
762
|
const [first, second, ...remaining] = parts
|
|
763
|
+
await ensureEnvLoaded({ createIfMissing: first !== 'deploy', quiet: first === 'deploy' })
|
|
757
764
|
|
|
758
765
|
// Handle init command directly
|
|
759
766
|
if (first === 'init') {
|
|
@@ -1461,6 +1468,19 @@ export async function run(argv = process.argv) {
|
|
|
1461
1468
|
} catch { /* @/cli may not exist in standalone apps — safe to ignore */ }
|
|
1462
1469
|
}
|
|
1463
1470
|
const all = modules.slice()
|
|
1471
|
+
|
|
1472
|
+
all.push({
|
|
1473
|
+
id: 'deploy',
|
|
1474
|
+
cli: [
|
|
1475
|
+
{
|
|
1476
|
+
command: 'railway',
|
|
1477
|
+
run: async (args: string[]) => {
|
|
1478
|
+
const { runRailwayDeploy } = await import('./lib/deploy/railway/index')
|
|
1479
|
+
await runRailwayDeploy(args)
|
|
1480
|
+
},
|
|
1481
|
+
},
|
|
1482
|
+
],
|
|
1483
|
+
} as Module)
|
|
1464
1484
|
|
|
1465
1485
|
// Built-in CLI module: queue
|
|
1466
1486
|
all.push({
|
|
@@ -2365,11 +2385,16 @@ export async function run(argv = process.argv) {
|
|
|
2365
2385
|
|
|
2366
2386
|
console.log('')
|
|
2367
2387
|
const started = Date.now()
|
|
2368
|
-
|
|
2388
|
+
const loggedArgs = modName === 'deploy' && cmdName === 'railway'
|
|
2389
|
+
? (await import('./lib/deploy/railway/options')).redactRailwayCliArgs(rest)
|
|
2390
|
+
: rest
|
|
2391
|
+
console.log(`🚀 Running ${modName}:${cmdName} ${loggedArgs.join(' ')}`)
|
|
2369
2392
|
try {
|
|
2370
2393
|
await cmd.run(rest)
|
|
2371
|
-
|
|
2372
|
-
|
|
2394
|
+
if (modName !== 'deploy' || cmdName !== 'railway') {
|
|
2395
|
+
const ms = Date.now() - started
|
|
2396
|
+
console.log(`⏱️ Done in ${ms}ms`)
|
|
2397
|
+
}
|
|
2373
2398
|
return 0
|
|
2374
2399
|
} catch (e: any) {
|
|
2375
2400
|
console.error(`💥 Failed: ${formatCliFailureMessage(modName, cmdName, e)}`)
|