@astrale-os/sdk 0.6.0-beta.13 → 0.6.0-beta.15

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 (175) hide show
  1. package/README.md +4 -0
  2. package/dist/deployment/adapter/adapter.d.ts +9 -11
  3. package/dist/deployment/adapter/legacy/adapter.d.ts +0 -10
  4. package/dist/deployment/adapter/legacy/deploy/context.d.ts +0 -2
  5. package/dist/deployment/adapter/legacy/deploy/result.d.ts +2 -3
  6. package/dist/deployment/adapter/legacy/index.d.ts +12 -12
  7. package/dist/deployment/adapter/legacy/prepare/context.d.ts +0 -2
  8. package/dist/deployment/adapter/release/context.d.ts +23 -3
  9. package/dist/deployment/adapter/release/index.d.ts +1 -0
  10. package/dist/deployment/adapter/release/list.d.ts +41 -0
  11. package/dist/deployment/adapter/release/list.js +1 -0
  12. package/dist/deployment/address/index.d.ts +1 -1
  13. package/dist/deployment/address/index.js +1 -0
  14. package/dist/deployment/address/summary.d.ts +31 -1
  15. package/dist/deployment/address/summary.js +117 -1
  16. package/dist/deployment/build/index.d.ts +1 -1
  17. package/dist/deployment/build/index.js +1 -1
  18. package/dist/deployment/build/manifest.d.ts +5 -0
  19. package/dist/deployment/build/manifest.js +10 -0
  20. package/dist/deployment/index.d.ts +1 -1
  21. package/dist/deployment/release/legacy/index.d.ts +17 -12
  22. package/dist/deployment/release/legacy/index.js +17 -12
  23. package/dist/deployment/verify/readiness/deployment.js +3 -13
  24. package/dist/deployment/verify/readiness/fetch.d.ts +13 -0
  25. package/dist/deployment/verify/readiness/fetch.js +26 -0
  26. package/dist/deployment/verify/readiness/identity.d.ts +3 -6
  27. package/dist/deployment/verify/readiness/identity.js +3 -3
  28. package/dist/deployment/verify/readiness/index.d.ts +12 -8
  29. package/dist/deployment/verify/readiness/index.js +15 -4
  30. package/dist/deployment/verify/readiness/legacy/index.d.ts +2 -0
  31. package/dist/deployment/verify/readiness/legacy/index.js +2 -0
  32. package/dist/deployment/verify/readiness/legacy/publication.d.ts +12 -0
  33. package/dist/deployment/verify/readiness/legacy/publication.js +23 -0
  34. package/dist/deployment/verify/readiness/release.d.ts +8 -0
  35. package/dist/deployment/verify/readiness/release.js +22 -0
  36. package/dist/execution/identity/authentication.d.ts +14 -3
  37. package/dist/execution/identity/authentication.js +16 -7
  38. package/dist/execution/identity/callback.d.ts +5 -2
  39. package/dist/execution/identity/callback.js +11 -5
  40. package/dist/execution/identity/delivery.d.ts +11 -4
  41. package/dist/execution/identity/delivery.js +47 -20
  42. package/dist/execution/identity/identity.d.ts +12 -6
  43. package/dist/execution/identity/identity.js +3 -3
  44. package/dist/execution/invocation/admission/authentication.d.ts +1 -2
  45. package/dist/execution/invocation/admission/authentication.js +1 -2
  46. package/dist/execution/invocation/admission/authority.d.ts +10 -4
  47. package/dist/execution/invocation/admission/authority.js +5 -1
  48. package/dist/execution/invocation/invocation.js +51 -14
  49. package/dist/execution/runtime/index.d.ts +1 -0
  50. package/dist/execution/runtime/index.js +1 -0
  51. package/dist/execution/runtime/load.d.ts +3 -3
  52. package/dist/execution/runtime/load.js +3 -2
  53. package/dist/execution/runtime/release.d.ts +22 -0
  54. package/dist/execution/runtime/release.js +14 -0
  55. package/dist/execution/serve.d.ts +2 -2
  56. package/dist/execution/service/create.js +9 -7
  57. package/dist/execution/service/release.d.ts +2 -2
  58. package/dist/execution/service/release.js +3 -2
  59. package/dist/execution/service/service.d.ts +9 -5
  60. package/dist/execution/service/serving/delivery.d.ts +6 -0
  61. package/dist/execution/service/{publication → serving}/delivery.js +5 -1
  62. package/dist/execution/service/{publication → serving}/index.d.ts +1 -1
  63. package/dist/execution/service/serving/installation.d.ts +4 -0
  64. package/dist/execution/service/serving/installation.js +9 -0
  65. package/dist/execution/service/serving/legacy/index.d.ts +15 -0
  66. package/dist/execution/service/serving/legacy/index.js +19 -0
  67. package/dist/execution/service/serving/source.d.ts +18 -0
  68. package/dist/execution/service/serving/source.js +19 -0
  69. package/dist/execution/workflows/run.js +1 -1
  70. package/dist/platform/versioning/name.d.ts +4 -3
  71. package/dist/platform/versioning/name.js +4 -3
  72. package/dist/project/define.js +58 -28
  73. package/dist/project/install-command.d.ts +10 -0
  74. package/dist/project/install-command.js +13 -0
  75. package/dist/project/project.d.ts +4 -2
  76. package/dist/tooling/cli/arguments.d.ts +1 -3
  77. package/dist/tooling/cli/arguments.js +51 -22
  78. package/dist/tooling/cli/commit.d.ts +23 -0
  79. package/dist/tooling/cli/commit.js +216 -0
  80. package/dist/tooling/cli/deploy-mode.d.ts +16 -13
  81. package/dist/tooling/cli/deploy-mode.js +45 -36
  82. package/dist/tooling/cli/deploy-result.d.ts +10 -7
  83. package/dist/tooling/cli/deploy-result.js +4 -3
  84. package/dist/tooling/cli/dotenv.d.ts +2 -2
  85. package/dist/tooling/cli/dotenv.js +3 -33
  86. package/dist/tooling/cli/help.js +73 -29
  87. package/dist/tooling/cli/immutable-deployment.d.ts +27 -0
  88. package/dist/tooling/cli/immutable-deployment.js +95 -0
  89. package/dist/tooling/cli/index.d.ts +3 -4
  90. package/dist/tooling/cli/index.js +2 -3
  91. package/dist/tooling/cli/legacy/deploy-result.d.ts +5 -3
  92. package/dist/tooling/cli/legacy/deploy-result.js +5 -3
  93. package/dist/tooling/cli/{development → legacy/development}/candidate.d.ts +3 -4
  94. package/dist/tooling/cli/{development → legacy/development}/candidate.js +7 -14
  95. package/dist/tooling/cli/legacy/development/deployment.d.ts +10 -0
  96. package/dist/tooling/cli/legacy/development/deployment.js +20 -0
  97. package/dist/tooling/cli/legacy/development/deprecation.d.ts +11 -0
  98. package/dist/tooling/cli/legacy/development/deprecation.js +13 -0
  99. package/dist/tooling/cli/legacy/development/develop.d.ts +15 -0
  100. package/dist/tooling/cli/legacy/development/develop.js +142 -0
  101. package/dist/tooling/cli/legacy/development/index.d.ts +21 -0
  102. package/dist/tooling/cli/legacy/development/index.js +21 -0
  103. package/dist/tooling/cli/legacy/development/report.d.ts +7 -0
  104. package/dist/tooling/cli/{development → legacy/development}/report.js +2 -5
  105. package/dist/tooling/cli/legacy/development/secrets-file.d.ts +2 -0
  106. package/dist/tooling/cli/legacy/development/secrets-file.js +33 -0
  107. package/dist/tooling/cli/legacy/stable-target-adapter.d.ts +5 -3
  108. package/dist/tooling/cli/legacy/stable-target-deploy.d.ts +40 -0
  109. package/dist/tooling/cli/legacy/stable-target-deploy.js +102 -0
  110. package/dist/tooling/cli/list.d.ts +114 -0
  111. package/dist/tooling/cli/list.js +297 -0
  112. package/dist/tooling/cli/orchestrate.d.ts +1 -1
  113. package/dist/tooling/cli/orchestrate.js +91 -95
  114. package/dist/tooling/cli/secrets.d.ts +9 -0
  115. package/dist/tooling/cli/secrets.js +19 -0
  116. package/package.json +7 -7
  117. package/dist/deployment/verify/readiness/publication.d.ts +0 -11
  118. package/dist/deployment/verify/readiness/publication.js +0 -41
  119. package/dist/execution/service/publication/delivery.d.ts +0 -2
  120. package/dist/execution/service/publication/installation.d.ts +0 -3
  121. package/dist/execution/service/publication/installation.js +0 -8
  122. package/dist/execution/service/publication/source.d.ts +0 -3
  123. package/dist/execution/service/publication/source.js +0 -11
  124. package/dist/tooling/cli/astrale/authority.d.ts +0 -5
  125. package/dist/tooling/cli/astrale/authority.js +0 -11
  126. package/dist/tooling/cli/astrale/executable.d.ts +0 -2
  127. package/dist/tooling/cli/astrale/executable.js +0 -47
  128. package/dist/tooling/cli/astrale/failure.d.ts +0 -6
  129. package/dist/tooling/cli/astrale/failure.js +0 -48
  130. package/dist/tooling/cli/astrale/instance.d.ts +0 -11
  131. package/dist/tooling/cli/astrale/instance.js +0 -52
  132. package/dist/tooling/cli/astrale/process.d.ts +0 -13
  133. package/dist/tooling/cli/astrale/process.js +0 -94
  134. package/dist/tooling/cli/astrale/version.d.ts +0 -18
  135. package/dist/tooling/cli/astrale/version.js +0 -55
  136. package/dist/tooling/cli/development/deployment.d.ts +0 -16
  137. package/dist/tooling/cli/development/deployment.js +0 -39
  138. package/dist/tooling/cli/development/develop.d.ts +0 -11
  139. package/dist/tooling/cli/development/develop.js +0 -243
  140. package/dist/tooling/cli/development/release-evidence.d.ts +0 -4
  141. package/dist/tooling/cli/development/release-evidence.js +0 -10
  142. package/dist/tooling/cli/development/report.d.ts +0 -9
  143. package/dist/tooling/cli/development/session-file.d.ts +0 -53
  144. package/dist/tooling/cli/development/session-file.js +0 -337
  145. package/dist/tooling/cli/development/target-lock.d.ts +0 -16
  146. package/dist/tooling/cli/development/target-lock.js +0 -68
  147. package/dist/tooling/cli/development/view.d.ts +0 -28
  148. package/dist/tooling/cli/development/view.js +0 -151
  149. package/dist/tooling/cli/environment-release.d.ts +0 -24
  150. package/dist/tooling/cli/environment-release.js +0 -46
  151. package/dist/tooling/cli/evidence.d.ts +0 -14
  152. package/dist/tooling/cli/evidence.js +0 -53
  153. package/dist/tooling/cli/installation/index.d.ts +0 -2
  154. package/dist/tooling/cli/installation/index.js +0 -1
  155. package/dist/tooling/cli/installation/observation.d.ts +0 -10
  156. package/dist/tooling/cli/installation/observation.js +0 -44
  157. package/dist/tooling/cli/installation/reconcile.d.ts +0 -32
  158. package/dist/tooling/cli/installation/reconcile.js +0 -122
  159. /package/dist/execution/service/{publication → serving}/index.js +0 -0
  160. /package/dist/tooling/cli/{credential.d.ts → legacy/development/credential.d.ts} +0 -0
  161. /package/dist/tooling/cli/{credential.js → legacy/development/credential.js} +0 -0
  162. /package/dist/tooling/cli/{development → legacy/development}/lease.d.ts +0 -0
  163. /package/dist/tooling/cli/{development → legacy/development}/lease.js +0 -0
  164. /package/dist/tooling/cli/{development → legacy/development}/lifecycle.d.ts +0 -0
  165. /package/dist/tooling/cli/{development → legacy/development}/lifecycle.js +0 -0
  166. /package/dist/tooling/cli/{development → legacy/development}/project-imports.d.ts +0 -0
  167. /package/dist/tooling/cli/{development → legacy/development}/project-imports.js +0 -0
  168. /package/dist/tooling/cli/{development → legacy/development}/project-lock.d.ts +0 -0
  169. /package/dist/tooling/cli/{development → legacy/development}/project-lock.js +0 -0
  170. /package/dist/tooling/cli/{development → legacy/development}/rebuild.d.ts +0 -0
  171. /package/dist/tooling/cli/{development → legacy/development}/rebuild.js +0 -0
  172. /package/dist/tooling/cli/{development → legacy/development}/source-watch.d.ts +0 -0
  173. /package/dist/tooling/cli/{development → legacy/development}/source-watch.js +0 -0
  174. /package/dist/tooling/cli/{development → legacy/development}/sources.d.ts +0 -0
  175. /package/dist/tooling/cli/{development → legacy/development}/sources.js +0 -0
@@ -0,0 +1,297 @@
1
+ import { acceptDeploymentRecord } from '../../deployment/address/index.js';
2
+ import { deploymentName } from '../../platform/versioning/name.js';
3
+ import { selectDeployMode } from './deploy-mode.js';
4
+ /**
5
+ * List the deployments of the selected Environments, one adapter `list` call each.
6
+ *
7
+ * Only an Environment that deploys immutable deployments (CT33 canonical) is listed; the others are
8
+ * `unlisted`, with the reason. A record the host returns but `acceptDeploymentRecord` refuses never
9
+ * fails the listing: that deployment is listed from what its host knows, its record `null`. This
10
+ * covers a host whose SDK still admits a record this SDK refuses; a host that refuses a stored
11
+ * record itself may fail its own list (Admin's Services does, AM-152). An Environment whose host
12
+ * cannot be listed, or whose adapter answers anything but deployments, is a failure, and the others
13
+ * are still listed.
14
+ */
15
+ export async function listDeployments(input) {
16
+ const deployments = [];
17
+ const unlisted = [];
18
+ const notices = [];
19
+ const failures = [];
20
+ for (const [environment, definition] of input.environments) {
21
+ const { adapter, parameters } = definition.deployment;
22
+ const name = `${adapter.name}@${adapter.version}`;
23
+ const mode = selectDeployMode({ command: 'deploy', environment, definition });
24
+ if (mode.kind !== 'canonical') {
25
+ unlisted.push(Object.freeze({
26
+ environment,
27
+ adapter: name,
28
+ reason: mode.kind,
29
+ }));
30
+ notices.push(mode.kind === 'legacy-direct'
31
+ ? `Environment ${environment} is not listed: it deploys in legacy direct mode, replacing ` +
32
+ 'one stable target in place, and has no immutable deployments.'
33
+ : `Environment ${environment} is not listed: deploy refuses its configuration. ` +
34
+ mode.refusals.map((refusal) => refusal.guidance).join(' '));
35
+ continue;
36
+ }
37
+ if (!('list' in adapter) || typeof adapter.list !== 'function') {
38
+ unlisted.push(Object.freeze({ environment, adapter: name, reason: 'unsupported' }));
39
+ notices.push(`Environment ${environment} is not listed: adapter ${name} lists no deployments.`);
40
+ continue;
41
+ }
42
+ let listed;
43
+ try {
44
+ listed = await adapter.list(parameters, {
45
+ origin: input.origin,
46
+ environment,
47
+ signal: input.signal,
48
+ ...(input.identity === undefined ? {} : { identity: input.identity }),
49
+ });
50
+ if (!Array.isArray(listed)) {
51
+ throw new TypeError(`Adapter ${name} listed no array of deployments.`);
52
+ }
53
+ for (const deployment of listed)
54
+ admitListed(deployment, name);
55
+ }
56
+ catch (cause) {
57
+ failures.push(Object.freeze({
58
+ environment,
59
+ adapter: name,
60
+ error: cause instanceof Error ? cause : new Error(String(cause)),
61
+ }));
62
+ continue;
63
+ }
64
+ for (const deployment of listed) {
65
+ const row = listedDeployment(deployment, { origin: input.origin, environment, adapter: name });
66
+ if (row.recordRefusal !== undefined) {
67
+ notices.push(deployment.record === undefined
68
+ ? `Deployment ${row.label} (${environment}) is listed from what its host knows: ` +
69
+ `${row.recordRefusal}`
70
+ : `Deployment ${row.label} (${environment}) is listed from what its host knows: its ` +
71
+ `stored record is not admitted. ${row.recordRefusal}`);
72
+ }
73
+ deployments.push(row);
74
+ }
75
+ }
76
+ return Object.freeze({
77
+ result: Object.freeze({
78
+ format: 'astrale.deployment-list',
79
+ version: 1,
80
+ origin: input.origin,
81
+ deployments: Object.freeze(deployments),
82
+ unlisted: Object.freeze(unlisted),
83
+ }),
84
+ notices: Object.freeze(notices),
85
+ failures: Object.freeze(failures),
86
+ });
87
+ }
88
+ /**
89
+ * One listed deployment, its record admitted when it describes exactly this deployment of this
90
+ * origin and Environment. Its name comes from the admitted record; otherwise it is named in the
91
+ * listed Environment, with the commit of the stored record only when that record names this
92
+ * deployment, origin and Environment, as `deploymentName` reads a commit.
93
+ */
94
+ export function listedDeployment(deployment, listing) {
95
+ let record = null;
96
+ let recordRefusal;
97
+ if (deployment.record === undefined) {
98
+ recordRefusal = NO_RECORD;
99
+ }
100
+ else {
101
+ try {
102
+ const admitted = acceptDeploymentRecord(deployment.record);
103
+ recordRefusal = describesAnother(admitted, deployment.label, listing);
104
+ if (recordRefusal === undefined)
105
+ record = admitted;
106
+ }
107
+ catch (cause) {
108
+ recordRefusal = cause instanceof Error ? cause.message : String(cause);
109
+ }
110
+ }
111
+ // No Publication names a deployment yet: the registry is read by `list` once versions exist.
112
+ const name = deploymentName(record ?? canonicalParts(deployment.record, deployment.label, listing), []);
113
+ return Object.freeze({
114
+ environment: listing.environment,
115
+ adapter: listing.adapter,
116
+ name,
117
+ id: deployment.id,
118
+ url: deployment.url,
119
+ label: deployment.label,
120
+ line: deployment.line,
121
+ owner: deployment.owner,
122
+ state: deployment.state,
123
+ retention: deployment.retention,
124
+ createdAt: deployment.createdAt,
125
+ ...(deployment.lastCallAt === undefined ? {} : { lastCallAt: deployment.lastCallAt }),
126
+ ...(deployment.expiresAt === undefined ? {} : { expiresAt: deployment.expiresAt }),
127
+ ...(deployment.retiredAt === undefined ? {} : { retiredAt: deployment.retiredAt }),
128
+ ...(deployment.retirement === undefined ? {} : { retirement: deployment.retirement }),
129
+ signingKeyId: deployment.signingKeyId,
130
+ record,
131
+ ...(recordRefusal === undefined ? {} : { recordRefusal }),
132
+ callTarget: deployment.callTarget,
133
+ });
134
+ }
135
+ /**
136
+ * Why a deployment its host keeps no record of has none: the adapter lists it without `record`, as a
137
+ * platform namespace lists a deployment it has not activated.
138
+ */
139
+ const NO_RECORD = 'Its host keeps no record of it: it has not been activated (a deploy still in progress, or one ' +
140
+ 'that stopped before activating it).';
141
+ /** Why an admitted record does not describe the listed deployment, if it does not. */
142
+ function describesAnother(record, label, listing) {
143
+ if (record.providerScript !== label) {
144
+ return `Deployment record describes deployment ${record.providerScript}, not ${label}.`;
145
+ }
146
+ if (record.origin !== listing.origin) {
147
+ return `Deployment record declares origin ${record.origin}, not ${listing.origin}.`;
148
+ }
149
+ if (record.environment !== listing.environment) {
150
+ return `Deployment record declares environment ${record.environment}, not ${listing.environment}.`;
151
+ }
152
+ return undefined;
153
+ }
154
+ /**
155
+ * What a deployment name may read of a stored record that is not admitted: the listed Environment,
156
+ * never the record's own, and the record's commit only when the record names this deployment, its
157
+ * origin and that Environment, each part kept only with the type the name reads. No digest: a
158
+ * record the listing does not admit is never named by a published version.
159
+ */
160
+ function canonicalParts(input, label, listing) {
161
+ const value = members(input);
162
+ // A record that does not say it describes this deployment says nothing of it, not its commit.
163
+ const describesThis = value?.providerScript === label &&
164
+ value.origin === listing.origin &&
165
+ value.environment === listing.environment;
166
+ const commit = describesThis ? members(value.commit) : undefined;
167
+ const base = members(commit?.base);
168
+ return {
169
+ environment: listing.environment,
170
+ commit: typeof commit?.sha === 'string'
171
+ ? {
172
+ sha: commit.sha,
173
+ dirty: commit.dirty === true,
174
+ ...(typeof base?.version === 'string' && typeof base.distance === 'number'
175
+ ? { base: { version: base.version, distance: base.distance } }
176
+ : {}),
177
+ }
178
+ : null,
179
+ };
180
+ }
181
+ const STATES = ['deploying', 'active', 'retiring', 'retired', 'failed'];
182
+ /**
183
+ * Admit one deployment an adapter listed, before any is kept: the facts a row prints, each of the
184
+ * type `DeploymentSummaryV1` gives it, and a call target an operator can act on. Anything else is
185
+ * that Environment's failure, never a row without its call target.
186
+ */
187
+ function admitListed(deployment, adapter) {
188
+ const invalid = (member) => {
189
+ throw new TypeError(`Adapter ${adapter} listed an invalid deployment (${member}).`);
190
+ };
191
+ const value = members(deployment) ?? invalid('deployment');
192
+ for (const key of ['id', 'url', 'label', 'line', 'owner', 'createdAt', 'signingKeyId']) {
193
+ if (!text(value[key]))
194
+ invalid(key);
195
+ }
196
+ for (const key of ['lastCallAt', 'expiresAt', 'retiredAt']) {
197
+ if (value[key] !== undefined && !text(value[key]))
198
+ invalid(key);
199
+ }
200
+ if (!STATES.includes(value.state))
201
+ invalid('state');
202
+ if (value.retention !== 'preview' && value.retention !== 'published')
203
+ invalid('retention');
204
+ if (value.retirement !== undefined &&
205
+ value.retirement !== 'expired' &&
206
+ value.retirement !== 'withdrawn') {
207
+ invalid('retirement');
208
+ }
209
+ const target = members(value.callTarget) ?? invalid('callTarget');
210
+ if (target.kind === 'services') {
211
+ if (!text(target.path) || (target.admin !== undefined && !text(target.admin))) {
212
+ invalid('callTarget');
213
+ }
214
+ }
215
+ else if (target.kind !== 'namespace' || !text(target.namespace) || !text(target.script)) {
216
+ invalid('callTarget');
217
+ }
218
+ }
219
+ function text(value) {
220
+ return typeof value === 'string' && value.length > 0;
221
+ }
222
+ function members(input) {
223
+ return input !== null && typeof input === 'object' && !Array.isArray(input)
224
+ ? input
225
+ : undefined;
226
+ }
227
+ const COLUMNS = ['RELEASE', 'VERSION', 'ENV', 'STATE', 'LAST CALL', 'EXPIRES', 'URL'];
228
+ const MINUTE = 60_000;
229
+ const HOUR = 60 * MINUTE;
230
+ const DAY = 24 * HOUR;
231
+ /**
232
+ * The listing as a table for a person, at `now`: each deployment's release, its name as computed
233
+ * (never shortened, AM-57), its Environment and state, its last call and its expiry, and the URL
234
+ * `astrale domain install` takes. Without deployments, it says so only for what was listed: nothing
235
+ * when a host `failed` (stderr says which), and that some Environments were not listed when any
236
+ * was not.
237
+ */
238
+ export function renderDeploymentList(result, now, failed = false) {
239
+ if (result.deployments.length === 0) {
240
+ if (failed)
241
+ return Object.freeze([]);
242
+ return Object.freeze([
243
+ result.unlisted.length === 0
244
+ ? `No deployments of ${result.origin}.`
245
+ : `No deployments of ${result.origin} in the Environments listed; ` +
246
+ `${result.unlisted.length} not listed (see above).`,
247
+ ]);
248
+ }
249
+ const rows = result.deployments.map((deployment) => [
250
+ deployment.record === null ? '?' : shortDigest(deployment.record.releaseDigest),
251
+ deployment.name,
252
+ deployment.environment,
253
+ deployment.state,
254
+ deployment.lastCallAt === undefined
255
+ ? '—'
256
+ : `${elapsed(now - Date.parse(deployment.lastCallAt))} ago`,
257
+ expiry(deployment, now),
258
+ deployment.url,
259
+ ]);
260
+ const widths = COLUMNS.map((column, index) => Math.max(column.length, ...rows.map((row) => row[index].length)));
261
+ const line = (cells) => cells
262
+ .map((cell, index) => (index === cells.length - 1 ? cell : cell.padEnd(widths[index])))
263
+ .join(' ');
264
+ return Object.freeze([line(COLUMNS), ...rows.map(line)]);
265
+ }
266
+ /** `sha256:` and the first seven hexadecimal digits, as git abbreviates a commit. */
267
+ function shortDigest(digest) {
268
+ return `${digest.slice(0, 'sha256:'.length + 7)}…`;
269
+ }
270
+ /**
271
+ * When a deployment stops serving: `never` once published, else its preview expiry as its host
272
+ * keeps it, 30 days after its activation or its last call, whichever is later (AM-123), with the
273
+ * calls counted since the host recorded them; `due` once that instant passed but the host has not
274
+ * retired it yet; for a retired deployment, why it was retired.
275
+ */
276
+ function expiry(deployment, now) {
277
+ if (deployment.state === 'retiring')
278
+ return 'retiring';
279
+ if (deployment.state === 'retired')
280
+ return deployment.retirement ?? 'retired';
281
+ if (deployment.retention === 'published')
282
+ return 'never';
283
+ if (deployment.expiresAt === undefined)
284
+ return '—';
285
+ const remaining = Date.parse(deployment.expiresAt) - now;
286
+ return remaining > 0 ? `in ${elapsed(remaining)}` : 'due';
287
+ }
288
+ /** A duration as a person reads it in a listing: minutes, then hours, then days. */
289
+ function elapsed(milliseconds) {
290
+ if (!(milliseconds >= MINUTE))
291
+ return '< 1 min';
292
+ if (milliseconds < HOUR)
293
+ return `${Math.floor(milliseconds / MINUTE)} min`;
294
+ if (milliseconds < DAY)
295
+ return `${Math.floor(milliseconds / HOUR)} h`;
296
+ return `${Math.floor(milliseconds / DAY)} d`;
297
+ }
@@ -1,5 +1,5 @@
1
1
  import { type ParsedArgs } from './arguments.js';
2
- import { develop } from './development/develop.js';
2
+ import { develop } from './legacy/development/index.js';
3
3
  export declare function run(argv: readonly string[]): Promise<number>;
4
4
  export declare function executeDevelopmentCommand(parsed: ParsedArgs, input: {
5
5
  readonly configPath: string;
@@ -1,21 +1,19 @@
1
1
  import { existsSync } from 'node:fs';
2
- import { dirname, isAbsolute, join, resolve } from 'node:path';
3
- import { admitRuntime, compile, resolveRuntime, verifyDeployment, verifyPreflight, } from '../../deployment/index.js';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { admitRuntime, compile, resolveRuntime } from '../../deployment/index.js';
4
4
  import { packageDomain } from '../packaging/index.js';
5
5
  import { parseArgs } from './arguments.js';
6
- import { resolveCompatibleAstrale } from './astrale/version.js';
7
6
  import { buildResult, bundleBuild, bundlerCopyWarnings, projectBundlers } from './build.js';
8
7
  import { BunRequiredError } from './bun.js';
8
+ import { selectDeployMode } from './deploy-mode.js';
9
9
  import { deprecationWarnings } from './deprecations.js';
10
- import { develop } from './development/develop.js';
11
- import { loadDeclaredSecrets } from './dotenv.js';
12
- import { reconcileEnvironmentRelease } from './environment-release.js';
13
10
  import { ERROR_USAGE, helpRequest, helpText } from './help.js';
14
- import { stableTargetDeployResult } from './legacy/deploy-result.js';
15
- import { stableTargetAdapter } from './legacy/stable-target-adapter.js';
16
- import { stableTargetBuildWarning } from './legacy/uploaded-build.js';
11
+ import { deployImmutable } from './immutable-deployment.js';
12
+ import { develop, developmentDeprecation } from './legacy/development/index.js';
13
+ import { deployStableTarget } from './legacy/stable-target-deploy.js';
14
+ import { listDeployments, renderDeploymentList } from './list.js';
17
15
  import { error, info, progress, warn } from './log.js';
18
- import { importModule, loadProject, runtimeEntrypoint, selectEnvironment } from './project.js';
16
+ import { importModule, loadProject, runtimeEntrypoint, selectEnvironment, } from './project.js';
19
17
  import { executeTests } from './test.js';
20
18
  const CONFIG_NAMES = ['astrale.config.ts', 'astrale.config.js', 'astrale.config.mjs'];
21
19
  export async function run(argv) {
@@ -54,16 +52,30 @@ export async function run(argv) {
54
52
  if (!process.versions.bun) {
55
53
  throw new BunRequiredError();
56
54
  }
55
+ // Under Bun, before any Project prerequisite, so a refused `dev` run still names its
56
+ // replacement. The Node launcher re-enters Bun through BunRequiredError, so it prints once.
57
+ if (parsed.command === 'dev')
58
+ warn(developmentDeprecation(parsed.env));
57
59
  const projectDir = process.cwd();
58
60
  const configPath = findConfig(projectDir);
59
61
  if (configPath === undefined) {
60
62
  error(`No astrale.config.ts found in ${projectDir}.`);
61
63
  return 1;
62
64
  }
65
+ const project = await loadProject(configPath);
66
+ if (parsed.command === 'list')
67
+ return list(parsed, project);
63
68
  if (parsed.command === 'dev') {
69
+ // `dev` deploys only legacy direct-mode Environments; any other is refused before any effect.
70
+ const mode = selectDeployMode({
71
+ command: 'dev',
72
+ environment: parsed.env,
73
+ definition: selectEnvironment(project, parsed.env),
74
+ });
75
+ if (refused(mode))
76
+ return 1;
64
77
  return executeDevelopmentCommand(parsed, { configPath, projectDir });
65
78
  }
66
- const project = await loadProject(configPath);
67
79
  if (parsed.command === 'test') {
68
80
  selectEnvironment(project, parsed.env);
69
81
  return executeTests({
@@ -111,97 +123,96 @@ export async function run(argv) {
111
123
  }
112
124
  }
113
125
  const environment = selectEnvironment(project, parsed.env);
126
+ // The configuration selects the deploy mode, before any effect (CT33).
127
+ const mode = selectDeployMode({
128
+ command: 'deploy',
129
+ environment: parsed.env,
130
+ definition: environment,
131
+ });
132
+ if (refused(mode))
133
+ return 1;
114
134
  // With `--json`, stdout carries only the DeployResultV1; progress goes to stderr.
115
135
  const report = progress(parsed.json === true);
116
- const adapter = stableTargetAdapter(environment.deployment.adapter);
117
- const parameters = environment.deployment.parameters;
118
136
  const lifecycle = deploymentLifecycle();
119
137
  try {
120
- // `deploy` rebuilds exactly as `build` does; preparation then only places that Bundle.
121
- const bundle = await bundleBuild({
122
- bundler: adapter.bundler,
123
- projectDir,
124
- build,
125
- runtime: resolved,
126
- signal: lifecycle.signal,
127
- });
128
- const source = await adapter.prepare(parameters, {
138
+ const deployment = {
129
139
  projectDir,
130
140
  environment: parsed.env,
131
- ...(environment.installation === undefined ? {} : { installation: environment.installation }),
132
- mode: 'release',
133
- signal: lifecycle.signal,
134
141
  build,
135
142
  runtime: resolved,
136
- bundle,
137
- });
138
- const artifact = verifyPreflight({ build, runtime: resolved, source });
139
- const secrets = secretsFor(adapter, parameters, projectDir, resolved.runtime);
140
- const installationExecutable = environment.installation === undefined || parsed.deployOnly
141
- ? undefined
142
- : await resolveCompatibleAstrale({ projectDir, signal: lifecycle.signal });
143
- const deployed = await adapter.deploy(parameters, {
144
- projectDir,
145
- environment: parsed.env,
146
- ...(environment.installation === undefined ? {} : { installation: environment.installation }),
147
143
  ...(parsed.identity === undefined ? {} : { identity: parsed.identity }),
148
- secrets,
149
- artifact,
150
144
  signal: lifecycle.signal,
151
- });
152
- const result = verifyDeployment(artifact, deployed);
153
- if (result.status === 'failed') {
154
- error(`Deployment failed during ${result.phase}: ${result.error.message}`);
145
+ report,
146
+ };
147
+ const deployed = mode.kind === 'canonical'
148
+ ? await deployImmutable({
149
+ ...deployment,
150
+ adapter: environment.deployment.adapter,
151
+ parameters: environment.deployment.parameters,
152
+ })
153
+ : await deployStableTarget({
154
+ ...deployment,
155
+ definition: environment,
156
+ reasons: mode.reasons,
157
+ });
158
+ if (deployed === undefined)
155
159
  return 1;
156
- }
157
- const uploaded = result.progress.provider.evidence.build;
158
- if (uploaded !== undefined) {
159
- // prepare/deploy places a stable target: a differing upload only warns.
160
- const warning = stableTargetBuildWarning(bundle, uploaded);
161
- if (warning !== undefined)
162
- warn(warning);
163
- }
164
- report(`Domain: ${result.release.publication.origin}`);
165
- report(`Environment: ${parsed.env}`);
166
- report(`Adapter: ${adapter.name}@${adapter.version}`);
167
- report(`Deployment: ${result.progress.provider.evidence.deployment}`);
168
- if (result.progress.provider.evidence.preflight !== undefined) {
169
- report(`Preflight: ${result.progress.provider.evidence.preflight}; public readiness verified`);
170
- }
171
- report(`Public: ${result.release.addressing.publicUrl}`);
172
- report(`Publication: ${result.release.publication.schema.revision} · ${result.release.publication.etag}`);
173
- const installed = await reconcileEnvironmentRelease({
174
- projectDir,
175
- environment: parsed.env,
176
- adapter,
177
- deployment: result,
178
- ...(environment.installation === undefined ? {} : { installation: environment.installation }),
179
- deployOnly: parsed.deployOnly ?? false,
180
- ...(installationExecutable === undefined ? {} : { installationExecutable }),
160
+ if (parsed.json === true)
161
+ process.stdout.write(`${JSON.stringify(deployed, null, 2)}\n`);
162
+ return 0;
163
+ }
164
+ finally {
165
+ lifecycle.dispose();
166
+ }
167
+ }
168
+ /**
169
+ * `astrale-domain list`: the deployments of every Environment, or of the one `--environment`
170
+ * names, as their hosts keep them for the caller. With `--json`, stdout carries only the
171
+ * ListResultV1, and only when every host answered; notices go to stderr.
172
+ */
173
+ async function list(parsed, project) {
174
+ const origin = compile(project.domain).schema.compiled.root.origin;
175
+ const environments = parsed.env === ''
176
+ ? Object.entries(project.environments)
177
+ : [[parsed.env, selectEnvironment(project, parsed.env)]];
178
+ const lifecycle = deploymentLifecycle();
179
+ try {
180
+ const listing = await listDeployments({
181
+ origin,
182
+ environments,
181
183
  ...(parsed.identity === undefined ? {} : { identity: parsed.identity }),
182
184
  signal: lifecycle.signal,
183
185
  });
184
- if (installed === undefined) {
185
- report('Installation: skipped');
186
- }
187
- else {
188
- report(`Installation: ${installed.instance} · ${installed.status}`);
186
+ for (const notice of listing.notices)
187
+ warn(notice);
188
+ for (const failure of listing.failures) {
189
+ error(`Environment ${failure.environment} could not be listed (${failure.adapter}): ` +
190
+ failure.error.message);
189
191
  }
190
- report('Ready: deployment verified');
191
192
  if (parsed.json === true) {
192
- const deployed = stableTargetDeployResult({
193
- environment: parsed.env,
194
- adapter,
195
- deployment: result,
196
- });
197
- process.stdout.write(`${JSON.stringify(deployed, null, 2)}\n`);
193
+ if (listing.failures.length > 0)
194
+ return 1;
195
+ process.stdout.write(`${JSON.stringify(listing.result, null, 2)}\n`);
196
+ return 0;
198
197
  }
199
- return 0;
198
+ const failed = listing.failures.length > 0;
199
+ for (const line of renderDeploymentList(listing.result, Date.now(), failed)) {
200
+ process.stdout.write(`${line}\n`);
201
+ }
202
+ return failed ? 1 : 0;
200
203
  }
201
204
  finally {
202
205
  lifecycle.dispose();
203
206
  }
204
207
  }
208
+ /** Report every refusal of a refused deploy mode, each with what to write instead. */
209
+ function refused(mode) {
210
+ if (mode.kind !== 'refused')
211
+ return false;
212
+ for (const refusal of mode.refusals)
213
+ error(refusal.guidance);
214
+ return true;
215
+ }
205
216
  export function executeDevelopmentCommand(parsed, input, execute = develop) {
206
217
  if (parsed.command !== 'dev')
207
218
  throw new TypeError('Development execution requires the dev command.');
@@ -209,10 +220,8 @@ export function executeDevelopmentCommand(parsed, input, execute = develop) {
209
220
  configPath: input.configPath,
210
221
  projectDir: input.projectDir,
211
222
  environment: parsed.env,
212
- deployOnly: parsed.deployOnly ?? false,
213
223
  ...(parsed.identity === undefined ? {} : { identity: parsed.identity }),
214
224
  ...(parsed.credentialFile === undefined ? {} : { credentialFile: parsed.credentialFile }),
215
- ...(parsed.sessionFile === undefined ? {} : { sessionFile: parsed.sessionFile }),
216
225
  ...(parsed.secretsFile === undefined ? {} : { secretsFile: parsed.secretsFile }),
217
226
  });
218
227
  }
@@ -234,19 +243,6 @@ async function lint(parsed) {
234
243
  throw cause;
235
244
  }
236
245
  }
237
- function secretsFor(adapter, parameters, projectDir, runtime) {
238
- const file = adapter.secretsFile?.(parameters);
239
- if (file === undefined)
240
- return Object.freeze({});
241
- const path = isAbsolute(file) ? file : join(projectDir, file);
242
- // An Integration-free Runtime has no Providers and therefore no author-owned
243
- // environment secrets. Adapter-owned secrets, such as the signing identity,
244
- // are resolved separately by the adapter and must not make this file required.
245
- if (Object.keys(runtime.integrations).length === 0 && !existsSync(path)) {
246
- return Object.freeze({});
247
- }
248
- return Object.freeze(loadDeclaredSecrets(path, file));
249
- }
250
246
  function findConfig(projectDir) {
251
247
  return CONFIG_NAMES.map((name) => join(projectDir, name)).find(existsSync);
252
248
  }
@@ -0,0 +1,9 @@
1
+ import type { Runtime } from '../../application/runtime/index.js';
2
+ import type { Adapter, LegacyAdapter } from '../../deployment/adapter/index.js';
3
+ /**
4
+ * The secrets one Environment's deployment binds: the values of the secrets file its adapter
5
+ * declares. An Integration-free Runtime has no Providers and therefore no author-owned secrets, so
6
+ * its file may be absent; adapter-owned secrets, such as a signing identity, are the adapter's to
7
+ * resolve and never make this file required.
8
+ */
9
+ export declare function environmentSecrets(adapter: Adapter<object> | LegacyAdapter<object>, parameters: object, projectDir: string, runtime: Runtime): Readonly<Record<string, string>>;
@@ -0,0 +1,19 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { isAbsolute, join } from 'node:path';
3
+ import { loadDeclaredSecrets } from './dotenv.js';
4
+ /**
5
+ * The secrets one Environment's deployment binds: the values of the secrets file its adapter
6
+ * declares. An Integration-free Runtime has no Providers and therefore no author-owned secrets, so
7
+ * its file may be absent; adapter-owned secrets, such as a signing identity, are the adapter's to
8
+ * resolve and never make this file required.
9
+ */
10
+ export function environmentSecrets(adapter, parameters, projectDir, runtime) {
11
+ const file = adapter.secretsFile?.(parameters);
12
+ if (file === undefined)
13
+ return Object.freeze({});
14
+ const path = isAbsolute(file) ? file : join(projectDir, file);
15
+ if (Object.keys(runtime.integrations).length === 0 && !existsSync(path)) {
16
+ return Object.freeze({});
17
+ }
18
+ return Object.freeze(loadDeclaredSecrets(path, file));
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/sdk",
3
- "version": "0.6.0-beta.13",
3
+ "version": "0.6.0-beta.15",
4
4
  "description": "Schema-first SDK for defining, composing, and deploying Astrale domains",
5
5
  "keywords": [
6
6
  "astrale",
@@ -253,11 +253,11 @@
253
253
  "registry": "https://registry.npmjs.org/"
254
254
  },
255
255
  "dependencies": {
256
- "@astrale-os/kernel-client": "0.6.0-beta.83",
257
- "@astrale-os/kernel-core": "0.9.0-beta.62",
258
- "@astrale-os/kernel-dsl": "0.2.0-beta.44",
259
- "@astrale-os/kernel-protocol": "0.5.0-beta.68",
260
- "@astrale-os/kernel-server": "0.5.0-beta.70",
256
+ "@astrale-os/kernel-client": "0.6.0-beta.84",
257
+ "@astrale-os/kernel-core": "0.9.0-beta.63",
258
+ "@astrale-os/kernel-dsl": "0.2.0-beta.45",
259
+ "@astrale-os/kernel-protocol": "0.5.0-beta.69",
260
+ "@astrale-os/kernel-server": "0.5.0-beta.71",
261
261
  "@lucide/icons": "1.34.0",
262
262
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
263
263
  "cross-spawn": "7.0.6",
@@ -333,7 +333,7 @@
333
333
  "e2e": "node __e2e__/flagship/runner.mjs",
334
334
  "typecheck": "tsgo --noEmit && pnpm run typecheck:workers && pnpm run typecheck:specs && pnpm run typecheck:types && pnpm run typecheck:tests && pnpm run typecheck:e2e && pnpm run typecheck:knowledge",
335
335
  "typecheck:workers": "tsgo --noEmit -p tsconfig.workers.json",
336
- "typecheck:specs": "tsgo --noEmit -p src/application/query/.spec/tsconfig.json && tsgo --noEmit -p src/platform/schema/.spec/tsconfig.json && tsgo --noEmit -p src/application/mutation/.spec/tsconfig.json && tsgo --noEmit -p src/tooling/cli/.spec/tsconfig.json",
336
+ "typecheck:specs": "tsgo --noEmit -p src/application/query/.spec/tsconfig.json && tsgo --noEmit -p src/platform/schema/.spec/tsconfig.json && tsgo --noEmit -p src/application/mutation/.spec/tsconfig.json && tsgo --noEmit -p src/tooling/cli/.spec/tsconfig.json && tsgo --noEmit -p src/tooling/cli/legacy/development/.spec/tsconfig.json",
337
337
  "typecheck:types": "tsgo --noEmit -p __tests__/tsconfig.types.json",
338
338
  "typecheck:tests": "tsgo --noEmit -p tsconfig.test.json",
339
339
  "typecheck:e2e": "tsgo --noEmit -p __e2e__/flagship/fixtures/tsconfig.v1.json && tsgo --noEmit -p __e2e__/flagship/fixtures/tsconfig.json",
@@ -1,11 +0,0 @@
1
- import * as publication from '@astrale-os/kernel-protocol/release/legacy';
2
- import type { Release } from '../../release/legacy/index.js';
3
- export type ReadinessFetch = (input: string, init: {
4
- readonly redirect: 'error';
5
- readonly signal?: AbortSignal;
6
- }) => Promise<Response>;
7
- /** Fetch and verify the exact live Publication and Bundle for one Release. */
8
- export declare function verifyPublication(release: Release, options?: {
9
- readonly fetch?: ReadinessFetch;
10
- readonly signal?: AbortSignal;
11
- }): Promise<publication.Publication>;