@tachyon-gg/railway-deploy 0.2.4 → 0.2.6

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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/dist/index.js +142 -74
  3. package/package.json +4 -2
package/README.md CHANGED
@@ -60,7 +60,7 @@ environment: production # Railway environment name
60
60
 
61
61
  shared_variables: # Variables shared across all services
62
62
  APP_ENV: production
63
- DATABASE_URL: ${{Postgres.DATABASE_URL}}
63
+ API_PORT: "8080"
64
64
 
65
65
  services: # Map of service name -> config
66
66
  web: { ... }
@@ -212,6 +212,8 @@ variables:
212
212
  | `%{service_name}` | At config load time | Built-in: the service's config key |
213
213
  | `null` | N/A | Marks a variable for deletion |
214
214
 
215
+ **Important:** Shared variables (`shared_variables`) cannot contain `${{service.VAR}}` references — Railway resolves shared variables without a service context, so cross-service references will resolve to empty strings. Use `${{service.VAR}}` references directly in service variables instead, and use shared variables only for plain values or `${{shared.OTHER_VAR}}` self-references.
216
+
215
217
  `%{param}` is expanded first, so it can be used inside `${{}}` Railway references. This is useful for templates that need to reference their own or other services' variables:
216
218
 
217
219
  ```yaml
package/dist/index.js CHANGED
@@ -38449,8 +38449,8 @@ var EgressGatewayAssociationCreateDocument = { kind: "Document", definitions: [{
38449
38449
  var EgressGatewayAssociationsClearDocument = { kind: "Document", definitions: [{ kind: "OperationDefinition", operation: "mutation", name: { kind: "Name", value: "EgressGatewayAssociationsClear" }, variableDefinitions: [{ kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "input" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "EgressGatewayServiceTargetInput" } } } }], selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "egressGatewayAssociationsClear" }, arguments: [{ kind: "Argument", name: { kind: "Name", value: "input" }, value: { kind: "Variable", name: { kind: "Name", value: "input" } } }] }] } }] };
38450
38450
  var ListProjectsDocument = { kind: "Document", definitions: [{ kind: "OperationDefinition", operation: "query", name: { kind: "Name", value: "ListProjects" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "projects" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "edges" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "node" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", name: { kind: "Name", value: "name" } }] } }] } }] } }] } }] };
38451
38451
  var GetProjectDocument = { kind: "Document", definitions: [{ kind: "OperationDefinition", operation: "query", name: { kind: "Name", value: "GetProject" }, variableDefinitions: [{ kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "id" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }], selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "project" }, arguments: [{ kind: "Argument", name: { kind: "Name", value: "id" }, value: { kind: "Variable", name: { kind: "Name", value: "id" } } }], selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", name: { kind: "Name", value: "name" } }, { kind: "Field", name: { kind: "Name", value: "environments" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "edges" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "node" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", name: { kind: "Name", value: "name" } }, { kind: "Field", name: { kind: "Name", value: "deploymentTriggers" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "edges" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "node" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", name: { kind: "Name", value: "branch" } }, { kind: "Field", name: { kind: "Name", value: "checkSuites" } }, { kind: "Field", name: { kind: "Name", value: "serviceId" } }, { kind: "Field", name: { kind: "Name", value: "repository" } }] } }] } }] } }] } }] } }] } }, { kind: "Field", name: { kind: "Name", value: "services" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "edges" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "node" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", name: { kind: "Name", value: "name" } }, { kind: "Field", name: { kind: "Name", value: "serviceInstances" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "edges" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "node" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "environmentId" } }, { kind: "Field", name: { kind: "Name", value: "source" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "image" } }, { kind: "Field", name: { kind: "Name", value: "repo" } }] } }, { kind: "Field", name: { kind: "Name", value: "domains" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "customDomains" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", name: { kind: "Name", value: "domain" } }, { kind: "Field", name: { kind: "Name", value: "targetPort" } }] } }, { kind: "Field", name: { kind: "Name", value: "serviceDomains" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", name: { kind: "Name", value: "domain" } }, { kind: "Field", name: { kind: "Name", value: "targetPort" } }] } }] } }, { kind: "Field", name: { kind: "Name", value: "region" } }, { kind: "Field", name: { kind: "Name", value: "numReplicas" } }, { kind: "Field", name: { kind: "Name", value: "restartPolicyType" } }, { kind: "Field", name: { kind: "Name", value: "restartPolicyMaxRetries" } }, { kind: "Field", name: { kind: "Name", value: "healthcheckPath" } }, { kind: "Field", name: { kind: "Name", value: "healthcheckTimeout" } }, { kind: "Field", name: { kind: "Name", value: "cronSchedule" } }, { kind: "Field", name: { kind: "Name", value: "startCommand" } }, { kind: "Field", name: { kind: "Name", value: "buildCommand" } }, { kind: "Field", name: { kind: "Name", value: "rootDirectory" } }, { kind: "Field", name: { kind: "Name", value: "dockerfilePath" } }, { kind: "Field", name: { kind: "Name", value: "preDeployCommand" } }, { kind: "Field", name: { kind: "Name", value: "sleepApplication" } }, { kind: "Field", name: { kind: "Name", value: "builder" } }, { kind: "Field", name: { kind: "Name", value: "watchPatterns" } }, { kind: "Field", name: { kind: "Name", value: "drainingSeconds" } }, { kind: "Field", name: { kind: "Name", value: "overlapSeconds" } }, { kind: "Field", name: { kind: "Name", value: "ipv6EgressEnabled" } }, { kind: "Field", name: { kind: "Name", value: "railwayConfigFile" } }] } }] } }] } }] } }] } }] } }, { kind: "Field", name: { kind: "Name", value: "buckets" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "edges" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "node" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", name: { kind: "Name", value: "name" } }] } }] } }] } }, { kind: "Field", name: { kind: "Name", value: "volumes" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "edges" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "node" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", name: { kind: "Name", value: "name" } }, { kind: "Field", name: { kind: "Name", value: "volumeInstances" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "edges" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "node" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", name: { kind: "Name", value: "mountPath" } }, { kind: "Field", name: { kind: "Name", value: "environmentId" } }, { kind: "Field", name: { kind: "Name", value: "serviceId" } }] } }] } }] } }] } }] } }] } }] } }] } }] };
38452
- var GetVariablesDocument = { kind: "Document", definitions: [{ kind: "OperationDefinition", operation: "query", name: { kind: "Name", value: "GetVariables" }, variableDefinitions: [{ kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "projectId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }, { kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "environmentId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }, { kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "serviceId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }], selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "variables" }, arguments: [{ kind: "Argument", name: { kind: "Name", value: "projectId" }, value: { kind: "Variable", name: { kind: "Name", value: "projectId" } } }, { kind: "Argument", name: { kind: "Name", value: "environmentId" }, value: { kind: "Variable", name: { kind: "Name", value: "environmentId" } } }, { kind: "Argument", name: { kind: "Name", value: "serviceId" }, value: { kind: "Variable", name: { kind: "Name", value: "serviceId" } } }] }] } }] };
38453
- var GetSharedVariablesDocument = { kind: "Document", definitions: [{ kind: "OperationDefinition", operation: "query", name: { kind: "Name", value: "GetSharedVariables" }, variableDefinitions: [{ kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "projectId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }, { kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "environmentId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }], selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "variables" }, arguments: [{ kind: "Argument", name: { kind: "Name", value: "projectId" }, value: { kind: "Variable", name: { kind: "Name", value: "projectId" } } }, { kind: "Argument", name: { kind: "Name", value: "environmentId" }, value: { kind: "Variable", name: { kind: "Name", value: "environmentId" } } }] }] } }] };
38452
+ var GetVariablesDocument = { kind: "Document", definitions: [{ kind: "OperationDefinition", operation: "query", name: { kind: "Name", value: "GetVariables" }, variableDefinitions: [{ kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "projectId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }, { kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "environmentId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }, { kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "serviceId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }], selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "variables" }, arguments: [{ kind: "Argument", name: { kind: "Name", value: "projectId" }, value: { kind: "Variable", name: { kind: "Name", value: "projectId" } } }, { kind: "Argument", name: { kind: "Name", value: "environmentId" }, value: { kind: "Variable", name: { kind: "Name", value: "environmentId" } } }, { kind: "Argument", name: { kind: "Name", value: "serviceId" }, value: { kind: "Variable", name: { kind: "Name", value: "serviceId" } } }, { kind: "Argument", name: { kind: "Name", value: "unrendered" }, value: { kind: "BooleanValue", value: true } }] }] } }] };
38453
+ var GetSharedVariablesDocument = { kind: "Document", definitions: [{ kind: "OperationDefinition", operation: "query", name: { kind: "Name", value: "GetSharedVariables" }, variableDefinitions: [{ kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "projectId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }, { kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "environmentId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }], selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "variables" }, arguments: [{ kind: "Argument", name: { kind: "Name", value: "projectId" }, value: { kind: "Variable", name: { kind: "Name", value: "projectId" } } }, { kind: "Argument", name: { kind: "Name", value: "environmentId" }, value: { kind: "Variable", name: { kind: "Name", value: "environmentId" } } }, { kind: "Argument", name: { kind: "Name", value: "unrendered" }, value: { kind: "BooleanValue", value: true } }] }] } }] };
38454
38454
  var GetTcpProxiesDocument = { kind: "Document", definitions: [{ kind: "OperationDefinition", operation: "query", name: { kind: "Name", value: "GetTcpProxies" }, variableDefinitions: [{ kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "serviceId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }, { kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "environmentId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }], selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "tcpProxies" }, arguments: [{ kind: "Argument", name: { kind: "Name", value: "serviceId" }, value: { kind: "Variable", name: { kind: "Name", value: "serviceId" } } }, { kind: "Argument", name: { kind: "Name", value: "environmentId" }, value: { kind: "Variable", name: { kind: "Name", value: "environmentId" } } }], selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", name: { kind: "Name", value: "applicationPort" } }, { kind: "Field", name: { kind: "Name", value: "proxyPort" } }, { kind: "Field", name: { kind: "Name", value: "domain" } }] } }] } }] };
38455
38455
  var GetServiceInstanceLimitsDocument = { kind: "Document", definitions: [{ kind: "OperationDefinition", operation: "query", name: { kind: "Name", value: "GetServiceInstanceLimits" }, variableDefinitions: [{ kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "serviceId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }, { kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "environmentId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }], selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "serviceInstanceLimits" }, arguments: [{ kind: "Argument", name: { kind: "Name", value: "serviceId" }, value: { kind: "Variable", name: { kind: "Name", value: "serviceId" } } }, { kind: "Argument", name: { kind: "Name", value: "environmentId" }, value: { kind: "Variable", name: { kind: "Name", value: "environmentId" } } }] }] } }] };
38456
38456
  var GetEgressGatewaysDocument = { kind: "Document", definitions: [{ kind: "OperationDefinition", operation: "query", name: { kind: "Name", value: "GetEgressGateways" }, variableDefinitions: [{ kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "serviceId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }, { kind: "VariableDefinition", variable: { kind: "Variable", name: { kind: "Name", value: "environmentId" } }, type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "String" } } } }], selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "egressGateways" }, arguments: [{ kind: "Argument", name: { kind: "Name", value: "serviceId" }, value: { kind: "Variable", name: { kind: "Name", value: "serviceId" } } }, { kind: "Argument", name: { kind: "Name", value: "environmentId" }, value: { kind: "Variable", name: { kind: "Name", value: "environmentId" } } }], selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "ipv4" } }, { kind: "Field", name: { kind: "Name", value: "region" } }] } }] } }] };
@@ -38893,7 +38893,19 @@ function yellow(text, noColor) {
38893
38893
  function dim(text, noColor) {
38894
38894
  return noColor ? text : `\x1B[2m${text}\x1B[0m`;
38895
38895
  }
38896
- var SENSITIVE_PATTERNS = ["PASSWORD", "SECRET", "TOKEN", "KEY", "PRIVATE", "CREDENTIAL"];
38896
+ var SENSITIVE_PATTERNS = [
38897
+ "PASSWORD",
38898
+ "PASSPHRASE",
38899
+ "SECRET",
38900
+ "TOKEN",
38901
+ "KEY",
38902
+ "PRIVATE",
38903
+ "CREDENTIAL",
38904
+ "JWT",
38905
+ "CERT",
38906
+ "SIGNING",
38907
+ "ENCRYPTION"
38908
+ ];
38897
38909
  function isSensitive(key) {
38898
38910
  const upper = key.toUpperCase();
38899
38911
  return SENSITIVE_PATTERNS.some((p) => upper.includes(p));
@@ -38913,22 +38925,25 @@ function describeChange(change) {
38913
38925
  if (change.cronSchedule)
38914
38926
  details.push(`cron: ${change.cronSchedule}`);
38915
38927
  return {
38916
- category: "Services",
38928
+ category: "Service",
38917
38929
  action: "create",
38918
- summary: `${change.name} (${details.join(", ")})`
38930
+ serviceName: change.name,
38931
+ summary: `create (${details.join(", ")})`
38919
38932
  };
38920
38933
  }
38921
38934
  case "delete-service":
38922
38935
  return {
38923
- category: "Services",
38936
+ category: "Service",
38924
38937
  action: "delete",
38925
- summary: `${change.name} (${change.serviceId})`
38938
+ serviceName: change.name,
38939
+ summary: `delete (${change.serviceId})`
38926
38940
  };
38927
38941
  case "update-service-settings":
38928
38942
  return {
38929
- category: "Service settings",
38943
+ category: "Settings",
38930
38944
  action: "update",
38931
- summary: `${change.serviceName}: ${Object.keys(change.settings).join(", ")}`
38945
+ serviceName: change.serviceName,
38946
+ summary: `settings: ${Object.keys(change.settings).join(", ")}`
38932
38947
  };
38933
38948
  case "update-deployment-trigger": {
38934
38949
  const parts = [];
@@ -38937,82 +38952,99 @@ function describeChange(change) {
38937
38952
  if (change.checkSuites !== undefined)
38938
38953
  parts.push(`checkSuites → ${change.checkSuites}`);
38939
38954
  return {
38940
- category: "Deployment triggers",
38955
+ category: "Trigger",
38941
38956
  action: "update",
38942
- summary: `${change.serviceName}: ${parts.join(", ")}`
38957
+ serviceName: change.serviceName,
38958
+ summary: `trigger: ${parts.join(", ")}`
38943
38959
  };
38944
38960
  }
38945
38961
  case "upsert-variables":
38946
38962
  return {
38947
- category: "Service variables",
38963
+ category: "Variables",
38948
38964
  action: "update",
38949
- summary: `${change.serviceName}: set ${Object.keys(change.variables).length} var(s) — ${Object.keys(change.variables).join(", ")}`
38965
+ serviceName: change.serviceName,
38966
+ summary: `set ${Object.keys(change.variables).length} var(s) — ${Object.keys(change.variables).join(", ")}`
38950
38967
  };
38951
38968
  case "delete-variables":
38952
38969
  return {
38953
- category: "Service variables",
38970
+ category: "Variables",
38954
38971
  action: "delete",
38955
- summary: `${change.serviceName}: delete ${change.variableNames.length} var(s) — ${change.variableNames.join(", ")}`
38972
+ serviceName: change.serviceName,
38973
+ summary: `delete ${change.variableNames.length} var(s) — ${change.variableNames.join(", ")}`
38956
38974
  };
38957
38975
  case "upsert-shared-variables":
38958
38976
  return {
38959
38977
  category: "Shared variables",
38960
38978
  action: "update",
38979
+ serviceName: null,
38961
38980
  summary: `set ${Object.keys(change.variables).length} var(s) — ${Object.keys(change.variables).join(", ")}`
38962
38981
  };
38963
38982
  case "delete-shared-variables":
38964
38983
  return {
38965
38984
  category: "Shared variables",
38966
38985
  action: "delete",
38986
+ serviceName: null,
38967
38987
  summary: `delete ${change.variableNames.length} var(s) — ${change.variableNames.join(", ")}`
38968
38988
  };
38969
38989
  case "create-domain": {
38970
38990
  const port = change.targetPort ? ` (port ${change.targetPort})` : "";
38971
38991
  return {
38972
- category: "Domains",
38992
+ category: "Domain",
38973
38993
  action: "create",
38974
- summary: `${change.serviceName}: ${change.domain}${port}`
38994
+ serviceName: change.serviceName,
38995
+ summary: `domain: ${change.domain}${port}`
38975
38996
  };
38976
38997
  }
38977
38998
  case "delete-domain":
38978
38999
  return {
38979
- category: "Domains",
39000
+ category: "Domain",
38980
39001
  action: "delete",
38981
- summary: `${change.serviceName}: ${change.domain}`
39002
+ serviceName: change.serviceName,
39003
+ summary: `domain: ${change.domain}`
38982
39004
  };
38983
39005
  case "create-service-domain": {
38984
39006
  const port = change.targetPort ? ` (port ${change.targetPort})` : "";
38985
39007
  return {
38986
- category: "Railway domains",
39008
+ category: "Railway domain",
38987
39009
  action: "create",
38988
- summary: `${change.serviceName}${port}`
39010
+ serviceName: change.serviceName,
39011
+ summary: `railway domain${port}`
38989
39012
  };
38990
39013
  }
38991
39014
  case "delete-service-domain":
38992
- return { category: "Railway domains", action: "delete", summary: change.serviceName };
39015
+ return {
39016
+ category: "Railway domain",
39017
+ action: "delete",
39018
+ serviceName: change.serviceName,
39019
+ summary: "railway domain"
39020
+ };
38993
39021
  case "create-volume":
38994
39022
  return {
38995
- category: "Volumes",
39023
+ category: "Volume",
38996
39024
  action: "create",
38997
- summary: `${change.serviceName}: ${change.mount}`
39025
+ serviceName: change.serviceName,
39026
+ summary: `volume: ${change.mount}`
38998
39027
  };
38999
39028
  case "delete-volume":
39000
39029
  return {
39001
- category: "Volumes",
39030
+ category: "Volume",
39002
39031
  action: "delete",
39003
- summary: `${change.serviceName} (${change.volumeId})`
39032
+ serviceName: change.serviceName,
39033
+ summary: `volume (${change.volumeId})`
39004
39034
  };
39005
39035
  case "create-tcp-proxy":
39006
39036
  return {
39007
- category: "TCP proxies",
39037
+ category: "TCP proxy",
39008
39038
  action: "create",
39009
- summary: `${change.serviceName}: port ${change.applicationPort}`
39039
+ serviceName: change.serviceName,
39040
+ summary: `tcp proxy: port ${change.applicationPort}`
39010
39041
  };
39011
39042
  case "delete-tcp-proxy":
39012
39043
  return {
39013
- category: "TCP proxies",
39044
+ category: "TCP proxy",
39014
39045
  action: "delete",
39015
- summary: `${change.serviceName}: proxy ${change.proxyId}`
39046
+ serviceName: change.serviceName,
39047
+ summary: `tcp proxy: ${change.proxyId}`
39016
39048
  };
39017
39049
  case "update-service-limits": {
39018
39050
  const parts = [];
@@ -39021,36 +39053,50 @@ function describeChange(change) {
39021
39053
  if (change.limits.vCPUs !== undefined)
39022
39054
  parts.push(`vCPUs: ${change.limits.vCPUs ?? "unset"}`);
39023
39055
  return {
39024
- category: "Resource limits",
39056
+ category: "Limits",
39025
39057
  action: "update",
39026
- summary: `${change.serviceName}: ${parts.join(", ")}`
39058
+ serviceName: change.serviceName,
39059
+ summary: `limits: ${parts.join(", ")}`
39027
39060
  };
39028
39061
  }
39029
39062
  case "enable-static-ips":
39030
39063
  return {
39031
- category: "Static outbound IPs",
39064
+ category: "Static IPs",
39032
39065
  action: "create",
39033
- summary: `${change.serviceName}: enable`
39066
+ serviceName: change.serviceName,
39067
+ summary: "static outbound IPs: enable"
39034
39068
  };
39035
39069
  case "disable-static-ips":
39036
39070
  return {
39037
- category: "Static outbound IPs",
39071
+ category: "Static IPs",
39038
39072
  action: "delete",
39039
- summary: `${change.serviceName}: disable`
39073
+ serviceName: change.serviceName,
39074
+ summary: "static outbound IPs: disable"
39040
39075
  };
39041
39076
  case "create-bucket":
39042
- return { category: "Buckets", action: "create", summary: change.bucketName };
39077
+ return {
39078
+ category: "Buckets",
39079
+ action: "create",
39080
+ serviceName: null,
39081
+ summary: change.bucketName
39082
+ };
39043
39083
  case "delete-bucket":
39044
- return { category: "Buckets", action: "delete", summary: change.name };
39084
+ return { category: "Buckets", action: "delete", serviceName: null, summary: change.name };
39045
39085
  default: {
39046
39086
  const _exhaustive = change;
39047
- return { category: "Unknown", action: "update", summary: _exhaustive.type };
39087
+ return {
39088
+ category: "Unknown",
39089
+ action: "update",
39090
+ serviceName: null,
39091
+ summary: _exhaustive.type
39092
+ };
39048
39093
  }
39049
39094
  }
39050
39095
  }
39051
39096
  function changeLabel(change) {
39052
39097
  const desc = describeChange(change);
39053
- return `${change.type}: ${desc.summary}`;
39098
+ const prefix = desc.serviceName ? `${desc.serviceName}: ` : "";
39099
+ return `${prefix}${desc.summary}`;
39054
39100
  }
39055
39101
  var ACTION_ICON = {
39056
39102
  create: (nc) => green("+", nc),
@@ -39069,44 +39115,60 @@ ${green("No changes needed", noColor)} — Railway matches desired state.
39069
39115
  console.log(`
39070
39116
  Changeset (${changeset.changes.length} changes):
39071
39117
  `);
39072
- const groups = new Map;
39073
- for (const change of changeset.changes) {
39074
- const desc = describeChange(change);
39075
- let group = groups.get(desc.category);
39118
+ const described = changeset.changes.map((change) => ({ change, desc: describeChange(change) }));
39119
+ const serviceGroups = new Map;
39120
+ for (const entry of described) {
39121
+ const key = entry.desc.serviceName;
39122
+ let group = serviceGroups.get(key);
39076
39123
  if (!group) {
39077
39124
  group = [];
39078
- groups.set(desc.category, group);
39125
+ serviceGroups.set(key, group);
39079
39126
  }
39080
- group.push({ change, desc });
39127
+ group.push(entry);
39081
39128
  }
39082
- for (const [category, entries] of groups) {
39083
- const actions = new Set(entries.map((e) => e.desc.action));
39084
- const headerAction = actions.size === 1 ? [...actions][0] : "update";
39129
+ const projectChanges = serviceGroups.get(null);
39130
+ if (projectChanges) {
39131
+ for (const { change, desc } of projectChanges) {
39132
+ const icon = ACTION_ICON[desc.action](noColor);
39133
+ if (verbose && change.type === "upsert-shared-variables") {
39134
+ console.log(` ${yellow("~", noColor)} Shared variables:`);
39135
+ for (const [key, value] of Object.entries(change.variables)) {
39136
+ const oldVal = options?.currentState?.sharedVariables[key];
39137
+ const oldStr = oldVal !== undefined ? maskValue(key, oldVal) : "(unset)";
39138
+ console.log(` ${icon} ${key}: ${dim(`"${oldStr}"`, noColor)} → ${dim(`"${maskValue(key, value)}"`, noColor)}`);
39139
+ }
39140
+ } else {
39141
+ console.log(` ${icon} ${desc.category}: ${desc.summary}`);
39142
+ }
39143
+ }
39144
+ console.log();
39145
+ serviceGroups.delete(null);
39146
+ }
39147
+ for (const [serviceName, entries] of serviceGroups) {
39148
+ const hasCreate = entries.some((e) => e.desc.action === "create" && e.desc.category === "Service");
39149
+ const hasDelete = entries.some((e) => e.desc.action === "delete" && e.desc.category === "Service");
39150
+ const headerAction = hasCreate ? "create" : hasDelete ? "delete" : "update";
39085
39151
  const headerIcon = ACTION_ICON[headerAction](noColor);
39086
- console.log(` ${headerIcon} ${category.toUpperCase()}:`);
39152
+ console.log(` ${headerIcon} ${serviceName}:`);
39087
39153
  for (const { change, desc } of entries) {
39088
39154
  const icon = ACTION_ICON[desc.action](noColor);
39089
39155
  if (verbose && change.type === "update-service-settings") {
39090
- console.log(` ${icon} ${change.serviceName}:`);
39091
39156
  for (const [key, value] of Object.entries(change.settings)) {
39157
+ if (isSensitive(key)) {
39158
+ console.log(` ${icon} ${key}: ***`);
39159
+ continue;
39160
+ }
39092
39161
  const currentSvc = options?.currentState?.services[change.serviceName];
39093
39162
  const oldVal = currentSvc ? currentSvc[key] : undefined;
39094
39163
  const oldStr = oldVal !== undefined ? JSON.stringify(oldVal) : "(unset)";
39095
39164
  const newStr = value === null ? "(unset)" : JSON.stringify(value);
39096
- console.log(` ${key}: ${oldStr} → ${newStr}`);
39165
+ console.log(` ${icon} ${key}: ${oldStr} → ${newStr}`);
39097
39166
  }
39098
39167
  } else if (verbose && change.type === "upsert-variables") {
39099
- console.log(` ${change.serviceName}:`);
39100
39168
  for (const [key, value] of Object.entries(change.variables)) {
39101
39169
  const currentSvc = options?.currentState?.services[change.serviceName];
39102
39170
  const oldVal = currentSvc?.variables[key];
39103
39171
  const oldStr = oldVal !== undefined ? maskValue(key, oldVal) : "(unset)";
39104
- console.log(` ${icon} ${key}: ${dim(`"${oldStr}"`, noColor)} → ${dim(`"${maskValue(key, value)}"`, noColor)}`);
39105
- }
39106
- } else if (verbose && change.type === "upsert-shared-variables") {
39107
- for (const [key, value] of Object.entries(change.variables)) {
39108
- const oldVal = options?.currentState?.sharedVariables[key];
39109
- const oldStr = oldVal !== undefined ? maskValue(key, oldVal) : "(unset)";
39110
39172
  console.log(` ${icon} ${key}: ${dim(`"${oldStr}"`, noColor)} → ${dim(`"${maskValue(key, value)}"`, noColor)}`);
39111
39173
  }
39112
39174
  } else {
@@ -39118,15 +39180,13 @@ Changeset (${changeset.changes.length} changes):
39118
39180
  let createCount = 0;
39119
39181
  let updateCount = 0;
39120
39182
  let deleteCount = 0;
39121
- for (const entries of groups.values()) {
39122
- for (const { desc } of entries) {
39123
- if (desc.action === "create")
39124
- createCount++;
39125
- else if (desc.action === "update")
39126
- updateCount++;
39127
- else
39128
- deleteCount++;
39129
- }
39183
+ for (const { desc } of described) {
39184
+ if (desc.action === "create")
39185
+ createCount++;
39186
+ else if (desc.action === "update")
39187
+ updateCount++;
39188
+ else
39189
+ deleteCount++;
39130
39190
  }
39131
39191
  const parts = [];
39132
39192
  if (createCount > 0)
@@ -39165,11 +39225,19 @@ async function applyChangeset(client, changeset, projectId, environmentId, optio
39165
39225
  const failed = [];
39166
39226
  const noColor = options?.noColor ?? false;
39167
39227
  const createdServiceIds = new Map;
39168
- const varChangeIndices = changeset.changes.map((c, i) => c.type === "upsert-variables" || c.type === "upsert-shared-variables" ? i : -1).filter((i) => i >= 0);
39169
- const lastVarChangeIdx = varChangeIndices.length > 0 ? varChangeIndices[varChangeIndices.length - 1] : -1;
39228
+ const lastVarChangeByService = new Map;
39229
+ for (let i = 0;i < changeset.changes.length; i++) {
39230
+ const c = changeset.changes[i];
39231
+ if (c.type === "upsert-variables") {
39232
+ lastVarChangeByService.set(c.serviceName, i);
39233
+ }
39234
+ }
39170
39235
  for (let i = 0;i < changeset.changes.length; i++) {
39171
39236
  const change = changeset.changes[i];
39172
- const skipDeploys = (change.type === "upsert-variables" || change.type === "upsert-shared-variables") && i < lastVarChangeIdx;
39237
+ let skipDeploys = false;
39238
+ if (change.type === "upsert-variables") {
39239
+ skipDeploys = i < (lastVarChangeByService.get(change.serviceName) ?? -1);
39240
+ }
39173
39241
  try {
39174
39242
  await applyChange(client, change, projectId, environmentId, createdServiceIds, skipDeploys);
39175
39243
  applied.push(change);
@@ -39633,12 +39701,12 @@ function diffServiceSettings(desired, current, changes) {
39633
39701
  if (desired.ipv6EgressEnabled !== current.ipv6EgressEnabled) {
39634
39702
  settings.ipv6EgressEnabled = desired.ipv6EgressEnabled ?? null;
39635
39703
  }
39636
- if (desired.registryCredentials) {
39637
- settings.registryCredentials = desired.registryCredentials;
39638
- }
39639
39704
  if (desired.railwayConfigFile !== current.railwayConfigFile) {
39640
39705
  settings.railwayConfigFile = desired.railwayConfigFile ?? null;
39641
39706
  }
39707
+ if (desired.registryCredentials) {
39708
+ settings.registryCredentials = desired.registryCredentials;
39709
+ }
39642
39710
  if (Object.keys(settings).length > 0) {
39643
39711
  changes.push({
39644
39712
  type: "update-service-settings",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tachyon-gg/railway-deploy",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,7 +29,8 @@
29
29
  "test:all": "bun test --bail --timeout 30000 --env-file .env.test test/",
30
30
  "lint": "biome check .",
31
31
  "lint:fix": "biome check --write .",
32
- "format": "biome format --write ."
32
+ "format": "biome format --write .",
33
+ "prepare": "husky"
33
34
  },
34
35
  "dependencies": {
35
36
  "@graphql-typed-document-node/core": "^3.2.0",
@@ -48,6 +49,7 @@
48
49
  "@graphql-codegen/typescript": "^4.1.2",
49
50
  "@graphql-codegen/typescript-operations": "^4.4.0",
50
51
  "@types/bun": "latest",
52
+ "husky": "^9.1.7",
51
53
  "typescript": "^5.7.0"
52
54
  }
53
55
  }