@kindgi/cli 0.1.8 → 0.1.9-rc.1

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 (270) hide show
  1. package/README.md +59 -2
  2. package/dist/api-address.d.ts +104 -12
  3. package/dist/api-address.d.ts.map +1 -1
  4. package/dist/api-address.js +169 -30
  5. package/dist/api-address.js.map +1 -1
  6. package/dist/build/host-install.d.ts.map +1 -1
  7. package/dist/build/host-install.js +5 -2
  8. package/dist/build/host-install.js.map +1 -1
  9. package/dist/cli.js +12 -2
  10. package/dist/cli.js.map +1 -1
  11. package/dist/commands/approvals.d.ts.map +1 -1
  12. package/dist/commands/approvals.js +15 -3
  13. package/dist/commands/approvals.js.map +1 -1
  14. package/dist/commands/auth.d.ts.map +1 -1
  15. package/dist/commands/auth.js +27 -0
  16. package/dist/commands/auth.js.map +1 -1
  17. package/dist/commands/build.d.ts +18 -0
  18. package/dist/commands/build.d.ts.map +1 -1
  19. package/dist/commands/build.js +118 -13
  20. package/dist/commands/build.js.map +1 -1
  21. package/dist/commands/conversations.d.ts.map +1 -1
  22. package/dist/commands/conversations.js +13 -3
  23. package/dist/commands/conversations.js.map +1 -1
  24. package/dist/commands/deploy.d.ts.map +1 -1
  25. package/dist/commands/deploy.js +235 -53
  26. package/dist/commands/deploy.js.map +1 -1
  27. package/dist/commands/deployments.d.ts +3 -0
  28. package/dist/commands/deployments.d.ts.map +1 -0
  29. package/dist/commands/deployments.js +196 -0
  30. package/dist/commands/deployments.js.map +1 -0
  31. package/dist/commands/doctor.d.ts +1 -5
  32. package/dist/commands/doctor.d.ts.map +1 -1
  33. package/dist/commands/doctor.js +42 -22
  34. package/dist/commands/doctor.js.map +1 -1
  35. package/dist/commands/egress.d.ts +9 -0
  36. package/dist/commands/egress.d.ts.map +1 -0
  37. package/dist/commands/egress.js +40 -0
  38. package/dist/commands/egress.js.map +1 -0
  39. package/dist/commands/env-scoped.d.ts +5 -0
  40. package/dist/commands/env-scoped.d.ts.map +1 -1
  41. package/dist/commands/env-scoped.js +64 -19
  42. package/dist/commands/env-scoped.js.map +1 -1
  43. package/dist/commands/env.d.ts.map +1 -1
  44. package/dist/commands/env.js +91 -30
  45. package/dist/commands/env.js.map +1 -1
  46. package/dist/commands/environments.d.ts +6 -0
  47. package/dist/commands/environments.d.ts.map +1 -0
  48. package/dist/commands/environments.js +166 -0
  49. package/dist/commands/environments.js.map +1 -0
  50. package/dist/commands/exports.d.ts.map +1 -1
  51. package/dist/commands/exports.js +118 -3
  52. package/dist/commands/exports.js.map +1 -1
  53. package/dist/commands/feedback.d.ts +1 -2
  54. package/dist/commands/feedback.d.ts.map +1 -1
  55. package/dist/commands/feedback.js +55 -24
  56. package/dist/commands/feedback.js.map +1 -1
  57. package/dist/commands/helpers.d.ts +6 -0
  58. package/dist/commands/helpers.d.ts.map +1 -1
  59. package/dist/commands/helpers.js +8 -0
  60. package/dist/commands/helpers.js.map +1 -1
  61. package/dist/commands/index.d.ts.map +1 -1
  62. package/dist/commands/index.js +12 -0
  63. package/dist/commands/index.js.map +1 -1
  64. package/dist/commands/license.d.ts +3 -0
  65. package/dist/commands/license.d.ts.map +1 -1
  66. package/dist/commands/license.js +1 -1
  67. package/dist/commands/license.js.map +1 -1
  68. package/dist/commands/people.d.ts.map +1 -1
  69. package/dist/commands/people.js +105 -10
  70. package/dist/commands/people.js.map +1 -1
  71. package/dist/commands/projects.d.ts.map +1 -1
  72. package/dist/commands/projects.js +133 -11
  73. package/dist/commands/projects.js.map +1 -1
  74. package/dist/commands/promote.d.ts +38 -0
  75. package/dist/commands/promote.d.ts.map +1 -0
  76. package/dist/commands/promote.js +371 -0
  77. package/dist/commands/promote.js.map +1 -0
  78. package/dist/commands/proposals.d.ts.map +1 -1
  79. package/dist/commands/proposals.js +2 -1
  80. package/dist/commands/proposals.js.map +1 -1
  81. package/dist/commands/providers.d.ts.map +1 -1
  82. package/dist/commands/providers.js +29 -5
  83. package/dist/commands/providers.js.map +1 -1
  84. package/dist/commands/schedules.d.ts.map +1 -1
  85. package/dist/commands/schedules.js +30 -2
  86. package/dist/commands/schedules.js.map +1 -1
  87. package/dist/commands/secrets.d.ts.map +1 -1
  88. package/dist/commands/secrets.js +65 -40
  89. package/dist/commands/secrets.js.map +1 -1
  90. package/dist/commands/sign-in.d.ts +3 -0
  91. package/dist/commands/sign-in.d.ts.map +1 -0
  92. package/dist/commands/sign-in.js +61 -0
  93. package/dist/commands/sign-in.js.map +1 -0
  94. package/dist/commands/sites.d.ts +3 -0
  95. package/dist/commands/sites.d.ts.map +1 -0
  96. package/dist/commands/sites.js +244 -0
  97. package/dist/commands/sites.js.map +1 -0
  98. package/dist/commands/tools.d.ts.map +1 -1
  99. package/dist/commands/tools.js +2 -2
  100. package/dist/commands/tools.js.map +1 -1
  101. package/dist/commands/types.d.ts +11 -0
  102. package/dist/commands/types.d.ts.map +1 -1
  103. package/dist/commands/webhooks.d.ts.map +1 -1
  104. package/dist/commands/webhooks.js +38 -2
  105. package/dist/commands/webhooks.js.map +1 -1
  106. package/dist/config.d.ts +13 -0
  107. package/dist/config.d.ts.map +1 -1
  108. package/dist/config.js +20 -7
  109. package/dist/config.js.map +1 -1
  110. package/dist/context.d.ts +38 -2
  111. package/dist/context.d.ts.map +1 -1
  112. package/dist/context.js +12 -1
  113. package/dist/context.js.map +1 -1
  114. package/dist/deploy/defaults.d.ts.map +1 -1
  115. package/dist/deploy/defaults.js +7 -1
  116. package/dist/deploy/defaults.js.map +1 -1
  117. package/dist/deploy/pack-service-text.d.ts +29 -0
  118. package/dist/deploy/pack-service-text.d.ts.map +1 -0
  119. package/dist/deploy/pack-service-text.js +72 -0
  120. package/dist/deploy/pack-service-text.js.map +1 -0
  121. package/dist/deploy/post.d.ts +17 -4
  122. package/dist/deploy/post.d.ts.map +1 -1
  123. package/dist/deploy/post.js +177 -60
  124. package/dist/deploy/post.js.map +1 -1
  125. package/dist/deploy/promotion-path.d.ts +44 -0
  126. package/dist/deploy/promotion-path.d.ts.map +1 -0
  127. package/dist/deploy/promotion-path.js +86 -0
  128. package/dist/deploy/promotion-path.js.map +1 -0
  129. package/dist/deploy/response.d.ts +3 -0
  130. package/dist/deploy/response.d.ts.map +1 -1
  131. package/dist/deploy/response.js +66 -12
  132. package/dist/deploy/response.js.map +1 -1
  133. package/dist/deploy/runners.d.ts +99 -5
  134. package/dist/deploy/runners.d.ts.map +1 -1
  135. package/dist/dev/java-builder.d.ts.map +1 -1
  136. package/dist/dev/java-builder.js +19 -3
  137. package/dist/dev/java-builder.js.map +1 -1
  138. package/dist/dev/python-builder.d.ts.map +1 -1
  139. package/dist/dev/python-builder.js +18 -3
  140. package/dist/dev/python-builder.js.map +1 -1
  141. package/dist/dev/runtime-container.d.ts +28 -3
  142. package/dist/dev/runtime-container.d.ts.map +1 -1
  143. package/dist/dev/runtime-container.js +103 -36
  144. package/dist/dev/runtime-container.js.map +1 -1
  145. package/dist/dev/runtime-image.d.ts +1 -1
  146. package/dist/dev/runtime-image.d.ts.map +1 -1
  147. package/dist/dev/runtime-image.js +1 -1
  148. package/dist/dev/runtime-image.js.map +1 -1
  149. package/dist/dev/scala-builder.d.ts.map +1 -1
  150. package/dist/dev/scala-builder.js +55 -21
  151. package/dist/dev/scala-builder.js.map +1 -1
  152. package/dist/dev/shared-watch.d.ts +30 -0
  153. package/dist/dev/shared-watch.d.ts.map +1 -1
  154. package/dist/dev/shared-watch.js +46 -1
  155. package/dist/dev/shared-watch.js.map +1 -1
  156. package/dist/env/pack-env-hint.d.ts +52 -0
  157. package/dist/env/pack-env-hint.d.ts.map +1 -0
  158. package/dist/env/pack-env-hint.js +57 -0
  159. package/dist/env/pack-env-hint.js.map +1 -0
  160. package/dist/env/pack-env-plan.d.ts +2 -0
  161. package/dist/env/pack-env-plan.d.ts.map +1 -1
  162. package/dist/env/pack-env-plan.js +2 -1
  163. package/dist/env/pack-env-plan.js.map +1 -1
  164. package/dist/env-projects.d.ts +23 -0
  165. package/dist/env-projects.d.ts.map +1 -0
  166. package/dist/env-projects.js +28 -0
  167. package/dist/env-projects.js.map +1 -0
  168. package/dist/exports/create.d.ts +71 -0
  169. package/dist/exports/create.d.ts.map +1 -0
  170. package/dist/exports/create.js +576 -0
  171. package/dist/exports/create.js.map +1 -0
  172. package/dist/exports/plan.d.ts +69 -0
  173. package/dist/exports/plan.d.ts.map +1 -0
  174. package/dist/exports/plan.js +496 -0
  175. package/dist/exports/plan.js.map +1 -0
  176. package/dist/exports/reader.d.ts +48 -0
  177. package/dist/exports/reader.d.ts.map +1 -0
  178. package/dist/exports/reader.js +181 -0
  179. package/dist/exports/reader.js.map +1 -0
  180. package/dist/feedback/entries.d.ts +53 -0
  181. package/dist/feedback/entries.d.ts.map +1 -0
  182. package/dist/feedback/entries.js +173 -0
  183. package/dist/feedback/entries.js.map +1 -0
  184. package/dist/feedback/intake.d.ts +37 -0
  185. package/dist/feedback/intake.d.ts.map +1 -0
  186. package/dist/feedback/intake.js +101 -0
  187. package/dist/feedback/intake.js.map +1 -0
  188. package/dist/feedback/protocol.d.ts +111 -0
  189. package/dist/feedback/protocol.d.ts.map +1 -0
  190. package/dist/feedback/protocol.js +143 -0
  191. package/dist/feedback/protocol.js.map +1 -0
  192. package/dist/feedback/redact.d.ts +34 -0
  193. package/dist/feedback/redact.d.ts.map +1 -0
  194. package/dist/feedback/redact.js +258 -0
  195. package/dist/feedback/redact.js.map +1 -0
  196. package/dist/feedback/report.d.ts +51 -0
  197. package/dist/feedback/report.d.ts.map +1 -0
  198. package/dist/feedback/report.js +122 -0
  199. package/dist/feedback/report.js.map +1 -0
  200. package/dist/feedback/run.d.ts +21 -0
  201. package/dist/feedback/run.d.ts.map +1 -0
  202. package/dist/feedback/run.js +578 -0
  203. package/dist/feedback/run.js.map +1 -0
  204. package/dist/feedback/store.d.ts +24 -0
  205. package/dist/feedback/store.d.ts.map +1 -0
  206. package/dist/feedback/store.js +117 -0
  207. package/dist/feedback/store.js.map +1 -0
  208. package/dist/main.d.ts +6 -0
  209. package/dist/main.d.ts.map +1 -1
  210. package/dist/main.js +86 -5
  211. package/dist/main.js.map +1 -1
  212. package/dist/node-version.d.ts +13 -0
  213. package/dist/node-version.d.ts.map +1 -0
  214. package/dist/node-version.js +37 -0
  215. package/dist/node-version.js.map +1 -0
  216. package/dist/parse.d.ts +8 -2
  217. package/dist/parse.d.ts.map +1 -1
  218. package/dist/parse.js +7 -2
  219. package/dist/parse.js.map +1 -1
  220. package/dist/project-ref.d.ts +44 -0
  221. package/dist/project-ref.d.ts.map +1 -0
  222. package/dist/project-ref.js +69 -0
  223. package/dist/project-ref.js.map +1 -0
  224. package/dist/sdk-skills/kindgi-authoring-agents/SKILL.md +15 -2
  225. package/dist/sdk-skills/kindgi-authoring-flows/SKILL.md +1 -1
  226. package/dist/sdk-skills/kindgi-authoring-guardrails/SKILL.md +44 -7
  227. package/dist/sdk-skills/kindgi-authoring-mcp-servers/SKILL.md +11 -2
  228. package/dist/sdk-skills/kindgi-authoring-providers/SKILL.md +60 -13
  229. package/dist/sdk-skills/kindgi-authoring-tools/SKILL.md +19 -2
  230. package/dist/sdk-skills/kindgi-framework-feedback/SKILL.md +82 -3
  231. package/dist/sdk-skills/kindgi-getting-started/SKILL.md +1 -1
  232. package/dist/sdk-skills/kindgi-java-authoring-agents/SKILL.md +17 -3
  233. package/dist/sdk-skills/kindgi-java-authoring-flows/SKILL.md +1 -1
  234. package/dist/sdk-skills/kindgi-java-authoring-guardrails/SKILL.md +43 -6
  235. package/dist/sdk-skills/kindgi-java-authoring-tools/SKILL.md +19 -2
  236. package/dist/sdk-skills/kindgi-java-getting-started/SKILL.md +1 -1
  237. package/dist/sdk-skills/kindgi-python-authoring-agents/SKILL.md +17 -3
  238. package/dist/sdk-skills/kindgi-python-authoring-flows/SKILL.md +1 -1
  239. package/dist/sdk-skills/kindgi-python-authoring-guardrails/SKILL.md +49 -13
  240. package/dist/sdk-skills/kindgi-python-authoring-tools/SKILL.md +27 -3
  241. package/dist/sdk-skills/kindgi-python-getting-started/SKILL.md +1 -1
  242. package/dist/sdk-skills/kindgi-scala-authoring-agents/SKILL.md +17 -3
  243. package/dist/sdk-skills/kindgi-scala-authoring-flows/SKILL.md +1 -1
  244. package/dist/sdk-skills/kindgi-scala-authoring-guardrails/SKILL.md +43 -6
  245. package/dist/sdk-skills/kindgi-scala-authoring-tools/SKILL.md +19 -2
  246. package/dist/sdk-skills/kindgi-scala-getting-started/SKILL.md +1 -1
  247. package/dist/sdk-skills/kindgi-using/SKILL.md +1 -1
  248. package/dist/slug.d.ts +18 -0
  249. package/dist/slug.d.ts.map +1 -0
  250. package/dist/slug.js +29 -0
  251. package/dist/slug.js.map +1 -0
  252. package/dist/templates/drupal/AGENTS.md +2 -0
  253. package/dist/templates/java/AGENTS.md +2 -0
  254. package/dist/templates/minimal/AGENTS.md +2 -0
  255. package/dist/templates/python/AGENTS.md +2 -0
  256. package/dist/templates/sample/AGENTS.md +2 -0
  257. package/dist/templates/scala/AGENTS.md +2 -0
  258. package/dist/templates/woocommerce/AGENTS.md +11 -4
  259. package/dist/templates/woocommerce/README.md.tmpl +47 -4
  260. package/dist/templates/woocommerce/agents/store-agent/index.test.ts.tmpl +13 -0
  261. package/dist/templates/woocommerce/agents/store-agent/index.ts.tmpl +11 -1
  262. package/dist/templates/woocommerce/tools/publish-product/index.ts.tmpl +7 -1
  263. package/dist/templates/woocommerce/tools/refund/index.ts.tmpl +7 -2
  264. package/dist/templates/woocommerce/tools/refund-large/index.ts.tmpl +8 -2
  265. package/dist/templates/woocommerce/tools/set-price/index.ts.tmpl +6 -0
  266. package/dist/value-target.d.ts +139 -0
  267. package/dist/value-target.d.ts.map +1 -0
  268. package/dist/value-target.js +231 -0
  269. package/dist/value-target.js.map +1 -0
  270. package/package.json +13 -13
@@ -28,21 +28,30 @@ import { stat } from 'node:fs/promises';
28
28
  import { isAbsolute, join, resolve } from 'node:path';
29
29
  import { LOCAL_ENV_NAME, packValues, readPackEnv } from '@kindgi/secrets-dotenv';
30
30
  import { deployApiAddress, hostedApiAddress } from '../api-address.js';
31
- import { HOSTED_BUILD } from '../build/hosted.js';
32
31
  import { isEnvelopeSigned } from '../deploy/envelope-loader.js';
32
+ import { packServiceProblemText, packServiceRetiredText } from '../deploy/pack-service-text.js';
33
33
  import { buildPostBody, deriveIdempotencyKey, deriveSyncSecretsIdempotencyKey, } from '../deploy/post.js';
34
+ import { promotionPath } from '../deploy/promotion-path.js';
34
35
  import { renderResponse } from '../deploy/response.js';
36
+ import { envProjectRefs } from '../env-projects.js';
37
+ import { packEnvProblemLines, packEnvSecretSetCommand, packEnvSetCommand, } from '../env/pack-env-hint.js';
35
38
  import { describePackEnvPlan, plainSecretReason, planPackEnv, } from '../env/pack-env-plan.js';
36
39
  import { renderJson } from '../output.js';
37
40
  import { loadPackConfig } from '../pack-config.js';
41
+ import { PROJECT_ID_RE } from '../project-ref.js';
38
42
  import { isWait, sendWaitingOutRateLimits } from '../rate-limit-wait.js';
43
+ import { SLUG } from '../slug.js';
44
+ import { environmentsOf, resolveValueTarget } from '../value-target.js';
39
45
  import { runBuild } from './build.js';
40
46
  /** Default relative path to look for a pre-built envelope. */
41
47
  export const DEFAULT_ENVELOPE_RELATIVE_PATH = '.kindgi/build/deploy-envelope.json';
42
48
  export const deployCommand = {
43
49
  kind: 'leaf',
44
50
  name: 'deploy',
45
- description: 'Deploy a signed pack image to Kindgi. Reads deploy-envelope.json (or runs `kindgi build` inline) + POSTs to /v1/deployments.',
51
+ description: 'Deploy a signed pack image to Kindgi. Reads deploy-envelope.json (or runs `kindgi build` inline) + POSTs to /v1/deployments. ' +
52
+ "`--hosted` (or the env block's `build: 'hosted'`) deploys to Kindgi hosted: an inline build runs on the hosted build service (as `kindgi build --hosted`), and after the deploy it waits, up to 5 minutes, for the deployment's pack service to be ready. " +
53
+ "A deploy into the Default project with a pack tool whose id is an MCP endpoint's tool's is refused (`tool-kind-mismatch`), and nothing is deployed.",
54
+ hostedRun: { defaultEnv: 'staging' },
46
55
  usage: 'kindgi deploy [--env <name>] [--from-envelope <path>] [--endpoint <url>] [--token <bearer>] ' +
47
56
  '[--tenant <id>] [--project <id|slug>] [--hosted] [--idempotency-key <str>] [--dry-run] [--sync-secrets] [--allow-missing-env] ' +
48
57
  '[--target <t>] [--build-endpoint <url>] [--out <dir>] [--artifact-version <v>] [--published-at <iso>] ' +
@@ -69,10 +78,6 @@ export const deployCommand = {
69
78
  type: 'string',
70
79
  description: "The project to deploy into, one per client: its id, or its slug. A slug two orgs share is refused: pass the id. Default: a tenant-wide deploy, into the tenant's Default project.",
71
80
  },
72
- hosted: {
73
- type: 'boolean',
74
- description: "Deploy to Kindgi hosted: an inline build runs on the hosted build service (as `kindgi build --hosted`), and after the deploy it waits, up to 5 minutes, for the deployment's pack service to be ready. Same as the env block's `build: 'hosted'`.",
75
- },
76
81
  'idempotency-key': {
77
82
  type: 'string',
78
83
  description: 'The `Idempotency-Key` header. Default: a hash of the request body, so the same image deployed again returns the same deployment.',
@@ -85,7 +90,7 @@ export const deployCommand = {
85
90
  // verticals rotate secrets on a separate cadence.
86
91
  'sync-secrets': {
87
92
  type: 'boolean',
88
- description: "After the deploy lands, also send the values in `.env.<env>` to the deployment's secrets. Off by default.",
93
+ description: "After the deploy lands, also send the values in `.env.<env>` to the deployment's secrets. Off by default. A hosted deploy needs --env with it.",
89
94
  },
90
95
  // Deploy although a name the pack's env.required declares has no
91
96
  // value for --env (the pack service won't be ready until it has).
@@ -104,7 +109,7 @@ export const deployCommand = {
104
109
  },
105
110
  out: {
106
111
  type: 'string',
107
- description: 'Where the envelope is looked for, and an inline build writes. Default: `.kindgi/build` under the pack root.',
112
+ description: 'Where the envelope is looked for, and an inline build writes (default: `.kindgi/build`). An inline build applies the same --out rules as `kindgi build`: not the pack root or a directory containing it, and under it for a TypeScript pack.',
108
113
  },
109
114
  'artifact-version': {
110
115
  type: 'string',
@@ -173,13 +178,49 @@ export async function runDeploy(ctx) {
173
178
  const envRefusal = checkPackEnv(args, line);
174
179
  if (envRefusal !== undefined)
175
180
  return envRefusal;
181
+ // On a runtime that keeps its values under one env, a pack environment
182
+ // with a `project` is that project, and its secret sync goes there, under
183
+ // the env the runtime serves. Any other env's sync is refused here,
184
+ // before a build or an upload, as the runtime would refuse the sync after
185
+ // the deployment.
186
+ let syncEnvName = args.envName;
187
+ let environmentProject;
188
+ const named = args.environments[args.envName] !== undefined;
189
+ if ((args.syncSecrets || named) && runners.servedEnv !== undefined) {
190
+ const target = resolveValueTarget({
191
+ flag: args.envName,
192
+ served: await runners.servedEnv({
193
+ endpoint: args.endpoint,
194
+ token: args.token,
195
+ fetchImpl: ctx.fetch,
196
+ ...(ctx.stopSignal !== undefined && { signal: ctx.stopSignal }),
197
+ }),
198
+ write: args.syncSecrets,
199
+ what: `the secrets in .env.${args.envName}`,
200
+ mayOmit: false,
201
+ environments: args.environments,
202
+ });
203
+ if (target.kind === 'refused')
204
+ return failure(target.stderr);
205
+ if (target.kind === 'ok' && target.environment !== undefined) {
206
+ environmentProject = target.environment.projectRef;
207
+ syncEnvName = target.envName;
208
+ line(` Project: ${environmentProject} (environments.${args.envName}.project)`);
209
+ if (args.syncSecrets)
210
+ line(` Secrets: under ${syncEnvName}, the env the runtime serves`);
211
+ line('');
212
+ }
213
+ }
176
214
  // ---- 1. The project to deploy into (--project) ---------------------
177
215
  //
178
216
  // Resolved first: a project that isn't there fails before a build, and
179
217
  // a hosted build carries the project's id as its label.
180
- const project = await resolveProject(args, runners, ctx);
218
+ const project = await resolveDeployProject(args, environmentProject, runners, ctx);
181
219
  if (project.kind === 'error')
182
220
  return failure(project.message);
221
+ const hostedEnvRefusal = refuseHostedConfigEnv(args, project.id);
222
+ if (hostedEnvRefusal !== undefined)
223
+ return hostedEnvRefusal;
183
224
  // ---- 2. Ensure envelope on disk -------------------------------------
184
225
  const envelopeReady = await ensureEnvelopeExists(ctx, args, runners, line, project.id);
185
226
  if (envelopeReady.kind === 'error')
@@ -286,6 +327,7 @@ export async function runDeploy(ctx) {
286
327
  ...(ctx.stopSignal !== undefined && { signal: ctx.stopSignal }),
287
328
  }), deployRefusal);
288
329
  // ---- 10. Render response ---------------------------------------------
330
+ const promotionPath = await promotionPathOf(args, runners, ctx, result);
289
331
  const rendered = renderResponse(result, {
290
332
  envName: args.envName,
291
333
  endpoint: args.endpoint,
@@ -296,6 +338,7 @@ export async function runDeploy(ctx) {
296
338
  signerKeyId: envelope.signerKeyId ?? '(unsigned)',
297
339
  envelopePath: args.envelopePath,
298
340
  idempotencyKey,
341
+ promotionPath,
299
342
  });
300
343
  for (const l of rendered.banner)
301
344
  line(l);
@@ -318,7 +361,7 @@ export async function runDeploy(ctx) {
318
361
  const syncSummary = { performed: false };
319
362
  const deployedRecord = result.kind === 'created' || result.kind === 'replayed' ? result.record : null;
320
363
  if (args.syncSecrets && deployedRecord !== null) {
321
- const syncOutcome = await runSecretsSync(ctx, args, runners, deployedRecord.deploymentId, line);
364
+ const syncOutcome = await runSecretsSync(ctx, args, syncEnvName, runners, deployedRecord.deploymentId, line);
322
365
  Object.assign(syncSummary, syncOutcome);
323
366
  }
324
367
  // ---- 12. Hosted: wait for the deployment's pack service --------------
@@ -327,7 +370,7 @@ export async function runDeploy(ctx) {
327
370
  // deployment is in place whatever the wait finds.
328
371
  let packService;
329
372
  if (args.hosted && deployedRecord !== null && deployedRecord.endpoint !== undefined) {
330
- const waited = await waitForPackService(ctx, args, runners, deployedRecord, line);
373
+ const waited = await waitForPackService(ctx, args, runners, deployedRecord, line, project.id);
331
374
  if (waited.kind === 'error') {
332
375
  return { kind: 'error', stderr: `${banner.join('\n')}\n${waited.message}`, exitCode: 1 };
333
376
  }
@@ -370,7 +413,9 @@ function deployRefusal(result) {
370
413
  * opted in via `--sync-secrets`. Warns loudly when secret-shaped keys
371
414
  * are present (they should live in `kindgi secrets set` instead).
372
415
  */
373
- async function runSecretsSync(ctx, args, runners, deploymentId, line) {
416
+ async function runSecretsSync(ctx, args,
417
+ /** The env the values go under: `--env`, or the one a runtime that keeps its values under one serves. */
418
+ envName, runners, deploymentId, line) {
374
419
  const file = await detectSecretsFile(args, '');
375
420
  if (file === null || file.entries.length === 0) {
376
421
  line(' --sync-secrets set, but no `.env.<envName>` entries to sync. Skipping.');
@@ -389,7 +434,7 @@ async function runSecretsSync(ctx, args, runners, deploymentId, line) {
389
434
  line('');
390
435
  }
391
436
  const syncBody = {
392
- envName: args.envName,
437
+ envName,
393
438
  secrets: file.entries.map((e) => ({ name: e.name, value: e.value })),
394
439
  };
395
440
  const idempotencyKey = deriveSyncSecretsIdempotencyKey(deploymentId, syncBody);
@@ -437,6 +482,10 @@ async function runSecretsSync(ctx, args, runners, deploymentId, line) {
437
482
  * value (unless `--allow-missing-env`) or a secret is given in the clear.
438
483
  */
439
484
  function checkPackEnv(args, line) {
485
+ // A hosted pack service gets the values set in the workspace, not these:
486
+ // `refuseHostedConfigEnv`, once the project is known.
487
+ if (args.hosted)
488
+ return undefined;
440
489
  const plan = args.packEnv;
441
490
  if (plan.entries.length === 0 && plan.problems.length === 0 && plan.warnings.length === 0) {
442
491
  return undefined;
@@ -456,6 +505,27 @@ function checkPackEnv(args, line) {
456
505
  }
457
506
  return undefined;
458
507
  }
508
+ /**
509
+ * A hosted deploy: Kindgi Cloud gives the pack service the env values set
510
+ * in the workspace (`kindgi env set`, a secret with `kindgi secrets set`),
511
+ * at the deployment's project, then its org, then the tenant.
512
+ * `environments.<env>.env` in kindgi.config isn't read there, so a config
513
+ * that gives any is refused before anything is built or sent, naming each
514
+ * name and how to set it.
515
+ */
516
+ function refuseHostedConfigEnv(args, projectId) {
517
+ const plan = args.packEnv;
518
+ if (!args.hosted || (plan.given.length === 0 && plan.problems.length === 0))
519
+ return undefined;
520
+ const block = `environments.${args.envName}.env`;
521
+ const names = plan.given.length === 0 ? '' : ` (${plan.given.join(', ')})`;
522
+ const target = { envName: args.envName, ...(projectId !== undefined && { projectId }) };
523
+ const command = packEnvSetCommand({ ...target, hasProjectMapping: false });
524
+ const secrets = plan.entries.some((e) => e.source?.kind === 'secret')
525
+ ? ` (a secret with \`${packEnvSecretSetCommand(target)}\`)`
526
+ : '';
527
+ return failure(`kindgi deploy: on Kindgi Cloud the pack service gets the env values set in your workspace, not ${block} in kindgi.config${names}. Set each with \`${command}\`${secrets}, then remove ${block} from kindgi.config and deploy again.\n`);
528
+ }
459
529
  // ---------------------------------------------------------------------
460
530
  // Envelope existence — either read from disk or inline-build
461
531
  // ---------------------------------------------------------------------
@@ -607,25 +677,15 @@ const PACK_SERVICE_WAIT_MS = 5 * 60 * 1000;
607
677
  const PACK_SERVICE_POLL_MS = 2_000;
608
678
  /** Reads in a row that may fail before it stops waiting. */
609
679
  const PACK_SERVICE_READ_TRIES = 3;
610
- /** Why a deployment has no pack service yet, by its `problem.code`. */
611
- const PACK_SERVICE_PROBLEMS = {
612
- 'tenant-not-provisioned': 'your workspace has nowhere to run pack services yet',
613
- 'capacity-unavailable': 'there is no room for another pack service just now',
614
- 'service-start-failed': "the pack's image was deployed but didn't start",
615
- 'image-not-allowed': "the image is outside your workspace's own repository",
616
- 'endpoint-url-invalid': "the platform couldn't connect the pack service",
617
- 'endpoint-host-not-allowed': "the platform couldn't connect the pack service",
618
- 'endpoint-name-mismatch': "the platform couldn't connect the pack service",
619
- 'endpoint-unreachable': "the pack service didn't answer",
620
- 'endpoint-info-mismatch': 'the pack service answered as another pack or version',
621
- };
622
680
  /**
623
681
  * Waits for the deployment's own pack service to be `ready`, reading the
624
- * deployment again every 2 s and saying each new state once. Fails when
625
- * it's `cleared`, after 5 minutes, after a few failed reads in a row, or
626
- * on Ctrl+C. The deployment is in place whatever it finds.
682
+ * deployment again every 2 s and saying each new state once. A `cleared`
683
+ * one that's `waiting` (a suspension that took it down was lifted) is
684
+ * waited for like a pending one. Fails when it's `cleared` otherwise (saying
685
+ * why, when the server says), after 5 minutes, after a few failed reads in
686
+ * a row, or on Ctrl+C. The deployment is in place whatever it finds.
627
687
  */
628
- async function waitForPackService(ctx, args, runners, record, line) {
688
+ async function waitForPackService(ctx, args, runners, record, line, projectId) {
629
689
  const read = runners.getDeployment;
630
690
  if (read === undefined) {
631
691
  return {
@@ -639,7 +699,17 @@ async function waitForPackService(ctx, args, runners, record, line) {
639
699
  const started = now();
640
700
  let endpoint = record.endpoint;
641
701
  let said = '';
702
+ let saidEnv = '';
642
703
  let failedReads = 0;
704
+ // What stands in the way of its env, said once, and again when it changes.
705
+ const sayPackEnv = async () => {
706
+ const lines = await packEnvLines(ctx, args, runners, id, projectId);
707
+ const text = lines.join('\n');
708
+ if (text !== '' && text !== saidEnv)
709
+ for (const l of lines)
710
+ line(` ${l}`);
711
+ saidEnv = text;
712
+ };
643
713
  line(' Waiting for the pack service');
644
714
  for (;;) {
645
715
  const settled = settledPackService(endpoint, id);
@@ -648,9 +718,14 @@ async function waitForPackService(ctx, args, runners, record, line) {
648
718
  line(' ✓ ready\n');
649
719
  return settled;
650
720
  }
651
- const state = pendingState(endpoint);
652
- if (state !== said)
721
+ const state = waitingState(endpoint);
722
+ if (state !== said) {
653
723
  line(` … ${state}`);
724
+ // At once, before the platform records it as the problem; and again then.
725
+ if (said === '' || endpoint.problem?.code.startsWith('pack-env-') === true) {
726
+ await sayPackEnv();
727
+ }
728
+ }
654
729
  said = state;
655
730
  if (now() - started >= PACK_SERVICE_WAIT_MS) {
656
731
  return stoppedWaiting(`The pack service isn't ready after 5 minutes: ${state}`, id);
@@ -671,6 +746,29 @@ async function waitForPackService(ctx, args, runners, record, line) {
671
746
  }
672
747
  }
673
748
  }
749
+ /**
750
+ * Where the deployment's pack declares names that stand in the way, said
751
+ * as what to do: nothing when none does, when the server doesn't give pack
752
+ * services their env (or is older), or when it can't be read.
753
+ */
754
+ async function packEnvLines(ctx, args, runners, deploymentId, projectId) {
755
+ if (runners.getPackEnv === undefined)
756
+ return [];
757
+ const got = await runners.getPackEnv({
758
+ endpoint: args.endpoint,
759
+ token: args.token,
760
+ deploymentId,
761
+ fetchImpl: ctx.fetch,
762
+ ...(ctx.stopSignal !== undefined && { signal: ctx.stopSignal }),
763
+ });
764
+ if (got.kind !== 'ok')
765
+ return [];
766
+ return packEnvProblemLines(got.packEnv.data, {
767
+ envName: got.packEnv.envName,
768
+ ...(projectId !== undefined && { projectId }),
769
+ hasProjectMapping: false,
770
+ });
771
+ }
674
772
  /** The deployment's pack service now, read again. */
675
773
  async function readPackService(read, ctx, args, deploymentId) {
676
774
  const got = await read({
@@ -689,24 +787,39 @@ function stoppedWaiting(why, deploymentId) {
689
787
  message: `${why}. deploymentId ${deploymentId}. Run the same \`kindgi deploy --hosted\` again to keep waiting: it sends the same deployment, and the server answers with this one.\n`,
690
788
  };
691
789
  }
692
- /** A pack service that's done with: `ready`, or `cleared` (a failure); else undefined. */
790
+ /**
791
+ * A pack service that's done with: `ready`, or `cleared` and not `waiting`
792
+ * (a failure, with the server's reason when it gives one: an older server
793
+ * gives none); else undefined.
794
+ */
693
795
  function settledPackService(endpoint, id) {
694
796
  if (endpoint.status === 'ready')
695
797
  return { kind: 'ok', endpoint };
696
- if (endpoint.status === 'cleared') {
798
+ if (endpoint.status !== 'cleared' || endpoint.waiting === true)
799
+ return undefined;
800
+ if (endpoint.retired === undefined) {
697
801
  return {
698
802
  kind: 'error',
699
- message: `The deployment's pack service was taken down before it was ready (cleared). deploymentId ${id}.\n`,
803
+ message: `The deployment's pack service was taken down (cleared). deploymentId ${id}.\n`,
700
804
  };
701
805
  }
702
- return undefined;
806
+ const why = packServiceRetiredText(endpoint.retired);
807
+ return {
808
+ kind: 'error',
809
+ message: `The deployment's pack service was taken down (cleared, ${endpoint.retired}): ${why}. deploymentId ${id}.\n`,
810
+ };
703
811
  }
704
- /** A pending pack service, as the wait says it: `pending`, and why when there's a problem. */
705
- function pendingState(endpoint) {
812
+ /**
813
+ * A pack service being waited for, as the wait says it: `pending`, or
814
+ * `cleared, waiting for its pack service again`, and why when there's a
815
+ * problem.
816
+ */
817
+ function waitingState(endpoint) {
818
+ const state = endpoint.status === 'cleared' ? 'cleared, waiting for its pack service again' : 'pending';
706
819
  if (endpoint.problem === undefined)
707
- return 'pending';
708
- const why = PACK_SERVICE_PROBLEMS[endpoint.problem.code] ?? 'not ready yet';
709
- return `pending (${endpoint.problem.code}): ${why}`;
820
+ return state;
821
+ const why = packServiceProblemText(endpoint.problem.code);
822
+ return `${state} (${endpoint.problem.code}): ${why}`;
710
823
  }
711
824
  function sleepUnlessStopped(ms, signal) {
712
825
  return new Promise((resolve) => {
@@ -719,18 +832,33 @@ function sleepUnlessStopped(ms, signal) {
719
832
  signal?.addEventListener('abort', done, { once: true });
720
833
  });
721
834
  }
722
- // ---------------------------------------------------------------------
723
- // The project a deploy goes into
724
- // ---------------------------------------------------------------------
725
- const PROJECT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
835
+ /**
836
+ * The project to deploy into: `--project`, else the pack environment's
837
+ * `project` when the runtime keeps its values under one env (`fromEnvironment`).
838
+ * A `--project` that isn't that environment's project is refused.
839
+ */
840
+ async function resolveDeployProject(args, fromEnvironment, runners, ctx) {
841
+ const flag = await resolveProject(args.projectRef, '--project', args, runners, ctx);
842
+ if (flag.kind === 'error' || fromEnvironment === undefined)
843
+ return flag;
844
+ const where = `environments.${args.envName}.project`;
845
+ const environment = await resolveProject(fromEnvironment, where, args, runners, ctx);
846
+ if (environment.kind === 'error' || args.projectRef === undefined)
847
+ return environment;
848
+ if (flag.id === environment.id)
849
+ return environment;
850
+ return {
851
+ kind: 'error',
852
+ message: `--project ${args.projectRef} isn't the ${args.envName} environment's project, ${fromEnvironment} (${where}). Leave --project out, or deploy with the --env whose project it is.\n`,
853
+ };
854
+ }
726
855
  /**
727
856
  * `--project`: an id goes as it is (the server answers 404 for one of no
728
857
  * project of the tenant); a slug is looked up, and refused when it names
729
858
  * no project, or several (a slug is unique within an org, so two orgs may
730
859
  * share one). Absent: tenant-wide.
731
860
  */
732
- async function resolveProject(args, runners, ctx) {
733
- const ref = args.projectRef;
861
+ async function resolveProject(ref, where, args, runners, ctx) {
734
862
  if (ref === undefined)
735
863
  return { kind: 'ok' };
736
864
  if (PROJECT_ID_RE.test(ref))
@@ -738,7 +866,7 @@ async function resolveProject(args, runners, ctx) {
738
866
  if (runners.findProjects === undefined) {
739
867
  return {
740
868
  kind: 'error',
741
- message: `--project ${ref}: this build can't look a slug up. Pass the project's id.\n`,
869
+ message: `${where} ${ref}: this build can't look a slug up. Pass the project's id.\n`,
742
870
  };
743
871
  }
744
872
  const found = await runners.findProjects({
@@ -751,21 +879,21 @@ async function resolveProject(args, runners, ctx) {
751
879
  if (found.kind === 'error') {
752
880
  return {
753
881
  kind: 'error',
754
- message: `--project ${ref}: couldn't list the tenant's projects: ${found.message}\n`,
882
+ message: `${where} ${ref}: couldn't list the tenant's projects: ${found.message}\n`,
755
883
  };
756
884
  }
757
885
  const [only, ...more] = found.projects;
758
886
  if (only === undefined) {
759
887
  return {
760
888
  kind: 'error',
761
- message: `--project ${ref}: no project of this tenant has that slug. \`kindgi projects list\` shows them.\n`,
889
+ message: `${where} ${ref}: no project of this tenant has that slug. \`kindgi projects list\` shows them; \`kindgi projects create --name=<name> --slug=${SLUG.test(ref) ? ref : '<slug>'}\` makes one.\n`,
762
890
  };
763
891
  }
764
892
  if (more.length > 0) {
765
893
  const ids = found.projects.map((p) => `${p.id} (${p.name}${p.orgId === undefined ? '' : `, org ${p.orgId}`})`);
766
894
  return {
767
895
  kind: 'error',
768
- message: `--project ${ref}: ${found.projects.length} projects of this tenant have that slug, in different orgs: ${ids.join(', ')}. Pass the id.\n`,
896
+ message: `${where} ${ref}: ${found.projects.length} projects of this tenant have that slug, in different orgs: ${ids.join(', ')}. Pass the id.\n`,
769
897
  };
770
898
  }
771
899
  return { kind: 'ok', id: only.id };
@@ -782,7 +910,8 @@ async function resolveDeployArgs(ctx) {
782
910
  const cfg = config.config;
783
911
  // --env — default staging (matches build's default).
784
912
  const envFlag = ctx.options.env;
785
- const envName = typeof envFlag === 'string' && envFlag !== '' ? envFlag : 'staging';
913
+ const envGiven = typeof envFlag === 'string' && envFlag !== '';
914
+ const envName = envGiven ? envFlag : 'staging';
786
915
  const envBlock = cfg.environments?.[envName] !== undefined
787
916
  ? cfg.environments[envName]
788
917
  : undefined;
@@ -790,14 +919,25 @@ async function resolveDeployArgs(ctx) {
790
919
  const outFlag = ctx.options.out;
791
920
  const outDirRaw = typeof outFlag === 'string' && outFlag !== '' ? outFlag : '.kindgi/build';
792
921
  const outDir = isAbsolute(outDirRaw) ? outDirRaw : resolve(packDir, outDirRaw);
793
- const hosted = ctx.options.hosted === true || envBlock?.build === HOSTED_BUILD;
794
- if (ctx.options.hosted === true && ctx.options['build-endpoint'] !== undefined) {
922
+ // --hosted, or the env block's `build: 'hosted'` (main.ts, `hostedRun`).
923
+ const hosted = ctx.hosted;
924
+ if (ctx.globals.hosted && ctx.options['build-endpoint'] !== undefined) {
795
925
  return {
796
926
  kind: 'error',
797
927
  stderr: '--hosted builds on the hosted build service, and --build-endpoint names another build server: leave one out.\n',
798
928
  exitCode: 1,
799
929
  };
800
930
  }
931
+ // A hosted runtime reads a deployment's secrets in the env it serves.
932
+ // Without --env the sync would send `.env.staging` under `staging`,
933
+ // which it may never read: the env is named, not defaulted.
934
+ if (hosted && ctx.options['sync-secrets'] === true && !envGiven) {
935
+ return {
936
+ kind: 'error',
937
+ stderr: "--sync-secrets on a hosted deploy needs --env. A hosted runtime reads a deployment's secrets in the env it serves; without --env, the values in .env.staging would go up under staging. Pass --env=<the env it serves>.\n",
938
+ exitCode: 1,
939
+ };
940
+ }
801
941
  // The API (api-address.ts): --endpoint > the env block's `endpoint` >
802
942
  // the CLI's own address (`--url`, KINDGI_API_URL or the stored one).
803
943
  // A stored key goes only to the address stored with it; a hosted
@@ -833,6 +973,9 @@ async function resolveDeployArgs(ctx) {
833
973
  exitCode: 1,
834
974
  };
835
975
  }
976
+ // No --endpoint or env `endpoint`: the key goes to the CLI's own address.
977
+ if (named.every((n) => n.url === undefined || n.url === ''))
978
+ ctx.noteOwnAddressKey();
836
979
  // --tenant — used only as a consistency check against the envelope.
837
980
  const tenantFlag = ctx.options.tenant;
838
981
  const explicitTenantId = typeof tenantFlag === 'string' && tenantFlag !== '' ? tenantFlag : undefined;
@@ -877,11 +1020,50 @@ async function resolveDeployArgs(ctx) {
877
1020
  dryRun,
878
1021
  syncSecrets,
879
1022
  envBlock,
1023
+ environments: environmentsOf(cfg),
880
1024
  packEnv: packEnv.plan,
881
1025
  allowMissingEnv: ctx.options['allow-missing-env'] === true,
882
1026
  },
883
1027
  };
884
1028
  }
1029
+ /**
1030
+ * A deploy refused into a later environment of its order
1031
+ * (`409 environment-takes-promotions`): each environment from the order's
1032
+ * first to that one, named from the pack config, for the hint. `undefined`
1033
+ * for any other answer, and when the refusal doesn't name the first (the
1034
+ * caller can't read it) or the reads fail: the hint then says the rule
1035
+ * without names.
1036
+ */
1037
+ async function promotionPathOf(args, runners, ctx, result) {
1038
+ if (result.kind !== 'wire-error' || result.error.code !== 'environment-takes-promotions') {
1039
+ return undefined;
1040
+ }
1041
+ const { position, firstProjectId } = result.error;
1042
+ if (position === undefined || firstProjectId === undefined)
1043
+ return undefined;
1044
+ if (runners.listProjects === undefined || runners.listEnvironmentOrders === undefined) {
1045
+ return undefined;
1046
+ }
1047
+ const opts = {
1048
+ endpoint: args.endpoint,
1049
+ token: args.token,
1050
+ fetchImpl: ctx.fetch,
1051
+ ...(ctx.stopSignal !== undefined && { signal: ctx.stopSignal }),
1052
+ };
1053
+ const [projects, orders] = await Promise.all([
1054
+ runners.listProjects(opts),
1055
+ runners.listEnvironmentOrders(opts),
1056
+ ]);
1057
+ if (projects.kind === 'error' || orders.kind === 'error')
1058
+ return undefined;
1059
+ return promotionPath({
1060
+ firstProjectId,
1061
+ position,
1062
+ orders: orders.orders,
1063
+ projects: projects.projects,
1064
+ environments: envProjectRefs(args.environments),
1065
+ });
1066
+ }
885
1067
  async function loadDeployConfig(ctx, packDir) {
886
1068
  const outcome = await loadPackConfig(ctx, packDir);
887
1069
  if (outcome.kind === 'ok')