@astrale-os/sdk 0.6.0-beta.14 → 0.6.0-beta.16

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 (98) hide show
  1. package/dist/deployment/adapter/adapter.d.ts +11 -7
  2. package/dist/deployment/adapter/index.d.ts +1 -0
  3. package/dist/deployment/adapter/index.js +1 -0
  4. package/dist/deployment/adapter/legacy/index.d.ts +6 -8
  5. package/dist/deployment/adapter/release/context.d.ts +7 -1
  6. package/dist/deployment/adapter/release/index.d.ts +1 -0
  7. package/dist/deployment/adapter/release/list.d.ts +41 -0
  8. package/dist/deployment/adapter/release/list.js +1 -0
  9. package/dist/deployment/adapter/release/refusal.d.ts +10 -0
  10. package/dist/deployment/adapter/release/refusal.js +14 -0
  11. package/dist/deployment/address/summary.d.ts +1 -1
  12. package/dist/deployment/index.d.ts +1 -1
  13. package/dist/deployment/release/legacy/index.d.ts +2 -2
  14. package/dist/deployment/release/legacy/index.js +2 -2
  15. package/dist/deployment/verify/readiness/legacy/publication.d.ts +1 -1
  16. package/dist/deployment/verify/readiness/legacy/publication.js +1 -1
  17. package/dist/execution/identity/authentication.d.ts +7 -3
  18. package/dist/execution/identity/authentication.js +9 -6
  19. package/dist/execution/identity/callback.d.ts +5 -2
  20. package/dist/execution/identity/callback.js +11 -5
  21. package/dist/execution/identity/identity.d.ts +5 -3
  22. package/dist/execution/identity/identity.js +1 -1
  23. package/dist/execution/workflows/run.js +1 -1
  24. package/dist/platform/versioning/name.d.ts +4 -3
  25. package/dist/platform/versioning/name.js +4 -3
  26. package/dist/project/define.js +12 -11
  27. package/dist/tooling/cli/arguments.d.ts +7 -1
  28. package/dist/tooling/cli/arguments.js +94 -10
  29. package/dist/tooling/cli/commit.d.ts +12 -0
  30. package/dist/tooling/cli/commit.js +19 -0
  31. package/dist/tooling/cli/deploy-mode.d.ts +25 -17
  32. package/dist/tooling/cli/deploy-mode.js +66 -12
  33. package/dist/tooling/cli/diff/declared.d.ts +7 -0
  34. package/dist/tooling/cli/diff/declared.js +27 -0
  35. package/dist/tooling/cli/diff/index.d.ts +30 -0
  36. package/dist/tooling/cli/diff/index.js +58 -0
  37. package/dist/tooling/cli/diff/render.d.ts +15 -0
  38. package/dist/tooling/cli/diff/render.js +131 -0
  39. package/dist/tooling/cli/diff/report.d.ts +88 -0
  40. package/dist/tooling/cli/diff/report.js +154 -0
  41. package/dist/tooling/cli/dotenv.d.ts +2 -2
  42. package/dist/tooling/cli/dotenv.js +3 -33
  43. package/dist/tooling/cli/help.js +148 -8
  44. package/dist/tooling/cli/immutable-deployment.d.ts +65 -9
  45. package/dist/tooling/cli/immutable-deployment.js +59 -19
  46. package/dist/tooling/cli/index.d.ts +9 -2
  47. package/dist/tooling/cli/index.js +6 -2
  48. package/dist/tooling/cli/{development → legacy/development}/candidate.d.ts +2 -2
  49. package/dist/tooling/cli/{development → legacy/development}/candidate.js +5 -5
  50. package/dist/tooling/cli/{development → legacy/development}/deployment.d.ts +1 -1
  51. package/dist/tooling/cli/{development → legacy/development}/deployment.js +1 -1
  52. package/dist/tooling/cli/legacy/development/deprecation.d.ts +11 -0
  53. package/dist/tooling/cli/legacy/development/deprecation.js +13 -0
  54. package/dist/tooling/cli/{development → legacy/development}/develop.d.ts +3 -0
  55. package/dist/tooling/cli/{development → legacy/development}/develop.js +7 -4
  56. package/dist/tooling/cli/legacy/development/index.d.ts +21 -0
  57. package/dist/tooling/cli/legacy/development/index.js +21 -0
  58. package/dist/tooling/cli/{development → legacy/development}/report.d.ts +1 -1
  59. package/dist/tooling/cli/{development → legacy/development}/report.js +1 -1
  60. package/dist/tooling/cli/legacy/development/secrets-file.d.ts +2 -0
  61. package/dist/tooling/cli/legacy/development/secrets-file.js +33 -0
  62. package/dist/tooling/cli/legacy/stable-target-deploy.d.ts +3 -5
  63. package/dist/tooling/cli/legacy/stable-target-deploy.js +3 -5
  64. package/dist/tooling/cli/list.d.ts +114 -0
  65. package/dist/tooling/cli/list.js +297 -0
  66. package/dist/tooling/cli/orchestrate.d.ts +1 -1
  67. package/dist/tooling/cli/orchestrate.js +153 -4
  68. package/dist/tooling/cli/publish/index.d.ts +74 -0
  69. package/dist/tooling/cli/publish/index.js +364 -0
  70. package/dist/tooling/cli/publish/report.d.ts +49 -0
  71. package/dist/tooling/cli/publish/report.js +15 -0
  72. package/dist/tooling/cli/registry/client.d.ts +127 -0
  73. package/dist/tooling/cli/registry/client.js +328 -0
  74. package/dist/tooling/cli/registry/executable.d.ts +20 -0
  75. package/dist/tooling/cli/registry/executable.js +79 -0
  76. package/dist/tooling/cli/registry/index.d.ts +6 -0
  77. package/dist/tooling/cli/registry/index.js +6 -0
  78. package/dist/tooling/cli/registry/process.d.ts +21 -0
  79. package/dist/tooling/cli/registry/process.js +84 -0
  80. package/dist/tooling/cli/yank.d.ts +34 -0
  81. package/dist/tooling/cli/yank.js +32 -0
  82. package/package.json +8 -8
  83. /package/dist/tooling/cli/{credential.d.ts → legacy/development/credential.d.ts} +0 -0
  84. /package/dist/tooling/cli/{credential.js → legacy/development/credential.js} +0 -0
  85. /package/dist/tooling/cli/{development → legacy/development}/lease.d.ts +0 -0
  86. /package/dist/tooling/cli/{development → legacy/development}/lease.js +0 -0
  87. /package/dist/tooling/cli/{development → legacy/development}/lifecycle.d.ts +0 -0
  88. /package/dist/tooling/cli/{development → legacy/development}/lifecycle.js +0 -0
  89. /package/dist/tooling/cli/{development → legacy/development}/project-imports.d.ts +0 -0
  90. /package/dist/tooling/cli/{development → legacy/development}/project-imports.js +0 -0
  91. /package/dist/tooling/cli/{development → legacy/development}/project-lock.d.ts +0 -0
  92. /package/dist/tooling/cli/{development → legacy/development}/project-lock.js +0 -0
  93. /package/dist/tooling/cli/{development → legacy/development}/rebuild.d.ts +0 -0
  94. /package/dist/tooling/cli/{development → legacy/development}/rebuild.js +0 -0
  95. /package/dist/tooling/cli/{development → legacy/development}/source-watch.d.ts +0 -0
  96. /package/dist/tooling/cli/{development → legacy/development}/source-watch.js +0 -0
  97. /package/dist/tooling/cli/{development → legacy/development}/sources.d.ts +0 -0
  98. /package/dist/tooling/cli/{development → legacy/development}/sources.js +0 -0
@@ -1,6 +1,19 @@
1
1
  import { isAbsolute, normalize } from 'node:path';
2
+ import { acceptVersion } from '../../platform/versioning/index.js';
2
3
  import { installCommand } from '../../project/install-command.js';
3
- export const COMMANDS = ['dev', 'build', 'deploy', 'test', 'lint', 'package', 'upgrade'];
4
+ export const COMMANDS = [
5
+ 'dev',
6
+ 'build',
7
+ 'deploy',
8
+ 'diff',
9
+ 'list',
10
+ 'publish',
11
+ 'test',
12
+ 'lint',
13
+ 'package',
14
+ 'upgrade',
15
+ 'yank',
16
+ ];
4
17
  /** Parse the frozen `astrale-domain` command grammar without performing effects. */
5
18
  export function parseArgs(argv) {
6
19
  const [command, ...rest] = argv;
@@ -9,6 +22,8 @@ export function parseArgs(argv) {
9
22
  const fix = rest.includes('--fix');
10
23
  const deployOnly = rest.includes('--deploy-only');
11
24
  const json = rest.includes('--json');
25
+ const undo = rest.includes('--undo');
26
+ const allowDirty = rest.includes('--allow-dirty');
12
27
  let secretsFile;
13
28
  let credentialFile;
14
29
  let identity;
@@ -54,7 +69,11 @@ export function parseArgs(argv) {
54
69
  else if (argument.startsWith('--suite=')) {
55
70
  suite = requiredValue('--suite', argument.slice('--suite='.length));
56
71
  }
57
- else if (argument === '--fix' || argument === '--deploy-only' || argument === '--json') {
72
+ else if (argument === '--fix' ||
73
+ argument === '--deploy-only' ||
74
+ argument === '--json' ||
75
+ argument === '--undo' ||
76
+ argument === '--allow-dirty') {
58
77
  continue;
59
78
  }
60
79
  else if (argument.startsWith('-')) {
@@ -66,9 +85,20 @@ export function parseArgs(argv) {
66
85
  }
67
86
  if (fix && command !== 'lint')
68
87
  throw new Error('`--fix` is only valid for `lint`.');
69
- if (json && command !== 'build' && command !== 'deploy') {
70
- throw new Error('`--json` is only valid for `build` or `deploy`.');
88
+ if (json &&
89
+ command !== 'build' &&
90
+ command !== 'deploy' &&
91
+ command !== 'diff' &&
92
+ command !== 'list' &&
93
+ command !== 'publish' &&
94
+ command !== 'yank') {
95
+ throw new Error('`--json` is only valid for `build`, `deploy`, `diff`, `list`, `publish` or `yank`.');
71
96
  }
97
+ if (allowDirty && command !== 'publish') {
98
+ throw new Error('`--allow-dirty` is only valid for `publish`.');
99
+ }
100
+ if (undo && command !== 'yank')
101
+ throw new Error('`--undo` is only valid for `yank`.');
72
102
  if (format !== undefined && command !== 'lint') {
73
103
  throw new Error('`--format` is only valid for `lint`.');
74
104
  }
@@ -80,13 +110,20 @@ export function parseArgs(argv) {
80
110
  }
81
111
  if (deployOnly && command !== 'dev' && command !== 'deploy')
82
112
  refuseDeployOnly(undefined);
83
- if (identity !== undefined && command !== 'dev' && command !== 'deploy') {
84
- throw new Error('`--as` is only valid for `dev` or `deploy`.');
113
+ if (identity !== undefined &&
114
+ command !== 'dev' &&
115
+ command !== 'deploy' &&
116
+ command !== 'diff' &&
117
+ command !== 'list' &&
118
+ command !== 'publish') {
119
+ throw new Error('`--as` is only valid for `dev`, `deploy`, `diff`, `list` or `publish`.');
85
120
  }
86
- if (environment !== undefined && command !== 'dev' && command !== 'deploy') {
87
- if (command !== 'test') {
88
- throw new Error('`--environment` is only valid for `dev`, `deploy`, or `test`.');
89
- }
121
+ if (environment !== undefined &&
122
+ command !== 'dev' &&
123
+ command !== 'deploy' &&
124
+ command !== 'list' &&
125
+ command !== 'test') {
126
+ throw new Error('`--environment` is only valid for `dev`, `deploy`, `list` or `test`.');
90
127
  }
91
128
  if (suite !== undefined && command !== 'test') {
92
129
  throw new Error('`--suite` is only valid for `test`.');
@@ -116,12 +153,47 @@ export function parseArgs(argv) {
116
153
  ...(secretsFile === undefined ? {} : { secretsFile }),
117
154
  };
118
155
  }
156
+ case 'list':
157
+ // Every Environment of the Project, or the one `--environment` names.
158
+ if (positionals.length > 0) {
159
+ throw new Error('`list` does not accept positional arguments; use `--environment`.');
160
+ }
161
+ return {
162
+ command: 'list',
163
+ env: environment ?? '',
164
+ ...(json ? { json: true } : {}),
165
+ ...(identity === undefined ? {} : { identity }),
166
+ };
119
167
  case 'build':
120
168
  case 'package':
121
169
  if (positionals.length > 0) {
122
170
  throw new Error(`\`${command}\` does not accept positional arguments.`);
123
171
  }
124
172
  return { command, env: '', ...(json ? { json: true } : {}) };
173
+ case 'diff':
174
+ // The version is package.json's: no command takes a version number (tech Versioning).
175
+ if (positionals.length > 0) {
176
+ throw new Error('`diff` does not accept positional arguments; it reads package.json.');
177
+ }
178
+ return {
179
+ command: 'diff',
180
+ env: '',
181
+ ...(json ? { json: true } : {}),
182
+ ...(identity === undefined ? {} : { identity }),
183
+ };
184
+ case 'publish': {
185
+ // The version is package.json's: the one positional names the Environment (tech Versioning).
186
+ if (positionals.length !== 1) {
187
+ throw new Error('`publish` takes exactly one Environment, e.g. `publish production`; it reads the version from package.json.');
188
+ }
189
+ return {
190
+ command: 'publish',
191
+ env: positionals[0],
192
+ ...(json ? { json: true } : {}),
193
+ ...(allowDirty ? { allowDirty: true } : {}),
194
+ ...(identity === undefined ? {} : { identity }),
195
+ };
196
+ }
125
197
  case 'test':
126
198
  if (positionals.length > 0) {
127
199
  throw new Error('`test` does not accept positional arguments; use `--environment`.');
@@ -131,6 +203,18 @@ export function parseArgs(argv) {
131
203
  env: environment ?? '',
132
204
  ...(suite === undefined ? {} : { suite }),
133
205
  };
206
+ case 'yank': {
207
+ if (positionals.length !== 1) {
208
+ throw new Error('`yank` takes exactly one published version, e.g. `yank 1.5.0`.');
209
+ }
210
+ return {
211
+ command: 'yank',
212
+ env: '',
213
+ version: acceptVersion(positionals[0]),
214
+ ...(undo ? { undo: true } : {}),
215
+ ...(json ? { json: true } : {}),
216
+ };
217
+ }
134
218
  case 'lint':
135
219
  if (positionals.length > 0)
136
220
  throw new Error('`lint` does not accept positional arguments.');
@@ -21,3 +21,15 @@ export declare function deploymentCommit(input: {
21
21
  readonly runtimeModule: string;
22
22
  readonly signal: AbortSignal;
23
23
  }): Promise<DeploymentCommitV1 | null>;
24
+ /**
25
+ * Whether a remote-tracking branch of the repository already holds a commit. A Publication records
26
+ * the commit it was built from, and release-please computes the next number from the pushed
27
+ * history: publishing a commit nobody pushed risks the same number being computed again (tech
28
+ * Versioning [.73872]). Nothing when git cannot tell. Reads the local remote-tracking refs only,
29
+ * never the network.
30
+ */
31
+ export declare function commitPushed(input: {
32
+ readonly projectDir: string;
33
+ readonly sha: string;
34
+ readonly signal: AbortSignal;
35
+ }): Promise<boolean | undefined>;
@@ -46,6 +46,25 @@ export async function deploymentCommit(input) {
46
46
  ...(base === undefined ? {} : { base }),
47
47
  });
48
48
  }
49
+ /**
50
+ * Whether a remote-tracking branch of the repository already holds a commit. A Publication records
51
+ * the commit it was built from, and release-please computes the next number from the pushed
52
+ * history: publishing a commit nobody pushed risks the same number being computed again (tech
53
+ * Versioning [.73872]). Nothing when git cannot tell. Reads the local remote-tracking refs only,
54
+ * never the network.
55
+ */
56
+ export async function commitPushed(input) {
57
+ if (!SHA.test(input.sha))
58
+ return undefined;
59
+ const branches = await git(input.projectDir, input.signal, [
60
+ 'branch',
61
+ '--remotes',
62
+ '--contains',
63
+ input.sha,
64
+ '--format=%(refname)',
65
+ ]);
66
+ return branches === undefined ? undefined : branches.length > 0;
67
+ }
49
68
  /**
50
69
  * The prefixes of the release tags of the package the Domain is released from, or nothing when
51
70
  * no release line holds the Domain.
@@ -1,11 +1,10 @@
1
- import type { LoadedEnvironment } from './project.js';
2
1
  /**
3
2
  * The deploy mode of one Environment for one command (CT33), chosen by the configuration itself:
4
3
  * an unchanged configuration keeps its mode, and so its behaviour, across an SDK upgrade, as long as
5
4
  * that mode still has a consumer (AM-16, AM-72).
6
5
  *
7
6
  * - `canonical`: an immutable deployment of one sealed release through the adapter's
8
- * `deployRelease`, with a key generated for it, and zero Kernel calls.
7
+ * `deployRelease`, with a key generated for it, and no call to any instance.
9
8
  * - `legacy-direct`: today's stable target, replaced in place through `prepare` and `deploy`, which
10
9
  * serves a v3 Publication only. Its `reasons` name what selected it.
11
10
  * - `refused`: a shape no deploy of this SDK serves, refused before any effect. Each refusal
@@ -35,32 +34,32 @@ export type LegacyDirectReason =
35
34
  | {
36
35
  readonly reason: 'no-namespace';
37
36
  }
38
- /**
39
- * An adapter that declares no `deployRelease` (the contract before adapter interface v2),
40
- * adapter-astrale's instance mode among them until adapter-astrale implements it.
41
- */
37
+ /** An adapter that declares no `deployRelease`: the contract before adapter interface v2. */
42
38
  | {
43
39
  readonly reason: 'no-deploy-release';
44
40
  };
45
41
  export type CloudflareDirectParameter = 'route' | 'workerName' | 'identityIssuer' | 'addressing' | 'signingIdentity';
46
42
  export interface DeployRefusal {
47
- readonly reason: 'astrale-instance' | 'astrale-signing-identity' | 'mixed-parameters' | 'scheduled-triggers' | 'development-canonical';
43
+ readonly reason: 'astrale-instance' | 'astrale-signing-identity' | 'mixed-parameters' | 'scheduled-triggers' | 'development-canonical' | 'publish-legacy-direct';
48
44
  /** The configuration or command to write instead, for the author. */
49
45
  readonly guidance: string;
50
46
  }
51
- /** The commands that select a deploy mode. `publish` refuses every Environment that is not canonical. */
52
- export type DeployModeCommand = 'deploy' | 'dev';
47
+ /**
48
+ * The commands that select a deploy mode. `publish` names only an immutable deployment, so it
49
+ * refuses every Environment that is not canonical (CT33, R-G03).
50
+ */
51
+ export type DeployModeCommand = 'deploy' | 'dev' | 'publish';
53
52
  /**
54
53
  * Select the deploy mode of one Environment (CT33). It reads the configuration and the command
55
54
  * only, and has no effect.
56
55
  *
57
- * A refusal wins over every other reason. Once adapter-astrale deploys immutable deployments
58
- * (`deployRelease`), its `instance` and `signingIdentity` are refused before any effect: their one
59
- * consumer pins an older SDK (AM-72). Until then they select its instance mode, on the legacy path
60
- * with every adapter that declares no `deployRelease`. Otherwise the Environment is on the legacy
61
- * direct-mode path when adapter-cloudflare names a direct-mode parameter or no `namespace`, or
62
- * when its adapter declares no `deployRelease`. Everything else is canonical, except under `dev`,
63
- * which serves the legacy direct-mode path only.
56
+ * A refusal wins over every other reason. adapter-astrale deploys immutable deployments only
57
+ * (`deployRelease`): its removed instance mode's `instance` and `signingIdentity` are refused
58
+ * before any effect, since their one consumer pins an older SDK (AM-72). Otherwise the Environment
59
+ * is on the legacy direct-mode path when adapter-cloudflare names a direct-mode parameter or no
60
+ * `namespace`, or when its adapter declares no `deployRelease`. Everything else is canonical,
61
+ * except under `dev`, which serves the legacy direct-mode path only. `publish` refuses the legacy
62
+ * direct-mode path: a stable target serves no release, so no version can name it.
64
63
  *
65
64
  * Within canonical adapter-cloudflare (with `namespace`), a direct-mode parameter is refused
66
65
  * rather than silently selecting either mode, and so is `wrangler.triggers`: an immutable
@@ -70,5 +69,14 @@ export type DeployModeCommand = 'deploy' | 'dev';
70
69
  export declare function selectDeployMode(input: {
71
70
  readonly command: DeployModeCommand;
72
71
  readonly environment: string;
73
- readonly definition: LoadedEnvironment;
72
+ /** The Environment's definition, of which selection reads only the adapter and its parameters. */
73
+ readonly definition: {
74
+ readonly deployment: {
75
+ readonly adapter: {
76
+ readonly name: string;
77
+ readonly deployRelease?: unknown;
78
+ };
79
+ readonly parameters: object;
80
+ };
81
+ };
74
82
  }): DeployMode;
@@ -2,6 +2,10 @@ import { installCommand } from '../../project/install-command.js';
2
2
  /** The adapter names of the first-party adapters whose parameters CT33 classifies. */
3
3
  const CLOUDFLARE = 'cloudflare';
4
4
  const ASTRALE = 'astrale';
5
+ /** The one Environment adapter-astrale deploys without an explicit organisation (AM-7). */
6
+ const DEVELOPMENT = 'development';
7
+ /** The parameters that named the stable Service of adapter-astrale's removed instance mode. */
8
+ const ASTRALE_SERVICE_PARAMETERS = ['serviceKey', 'name'];
5
9
  const CLOUDFLARE_DIRECT_PARAMETERS = [
6
10
  'route',
7
11
  'workerName',
@@ -13,13 +17,13 @@ const CLOUDFLARE_DIRECT_PARAMETERS = [
13
17
  * Select the deploy mode of one Environment (CT33). It reads the configuration and the command
14
18
  * only, and has no effect.
15
19
  *
16
- * A refusal wins over every other reason. Once adapter-astrale deploys immutable deployments
17
- * (`deployRelease`), its `instance` and `signingIdentity` are refused before any effect: their one
18
- * consumer pins an older SDK (AM-72). Until then they select its instance mode, on the legacy path
19
- * with every adapter that declares no `deployRelease`. Otherwise the Environment is on the legacy
20
- * direct-mode path when adapter-cloudflare names a direct-mode parameter or no `namespace`, or
21
- * when its adapter declares no `deployRelease`. Everything else is canonical, except under `dev`,
22
- * which serves the legacy direct-mode path only.
20
+ * A refusal wins over every other reason. adapter-astrale deploys immutable deployments only
21
+ * (`deployRelease`): its removed instance mode's `instance` and `signingIdentity` are refused
22
+ * before any effect, since their one consumer pins an older SDK (AM-72). Otherwise the Environment
23
+ * is on the legacy direct-mode path when adapter-cloudflare names a direct-mode parameter or no
24
+ * `namespace`, or when its adapter declares no `deployRelease`. Everything else is canonical,
25
+ * except under `dev`, which serves the legacy direct-mode path only. `publish` refuses the legacy
26
+ * direct-mode path: a stable target serves no release, so no version can name it.
23
27
  *
24
28
  * Within canonical adapter-cloudflare (with `namespace`), a direct-mode parameter is refused
25
29
  * rather than silently selecting either mode, and so is `wrangler.triggers`: an immutable
@@ -37,17 +41,17 @@ export function selectDeployMode(input) {
37
41
  if (declares(parameters, 'instance')) {
38
42
  refusals.push({
39
43
  reason: 'astrale-instance',
40
- guidance: `astrale({ instance: '${String(parameters.instance)}' }) deployed through that ` +
41
- "instance's Services, which no longer hosts deployments. Remove `instance`: " +
42
- `\`astrale-domain deploy ${environment}\` makes an immutable deployment on Admin's ` +
43
- `Services, and \`${installCommand(String(parameters.instance))}\` installs it.`,
44
+ guidance: astraleInstanceGuidance(parameters, environment),
44
45
  });
45
46
  }
46
47
  if (declares(parameters, 'signingIdentity')) {
47
48
  refusals.push({
48
49
  reason: 'astrale-signing-identity',
49
50
  guidance: 'astrale({ signingIdentity }) is no longer accepted: each deployment gets a signing key ' +
50
- 'generated when it is deployed. Remove `signingIdentity`.',
51
+ 'generated when it is deployed. Remove `signingIdentity`' +
52
+ // With `instance`, its own refusal already names the organisation.
53
+ (declares(parameters, 'instance') ? '' : organizationGuidance(parameters, environment)) +
54
+ '.',
51
55
  });
52
56
  }
53
57
  }
@@ -80,6 +84,14 @@ export function selectDeployMode(input) {
80
84
  reasons.push({ reason: 'no-deploy-release' });
81
85
  if (refusals.length > 0)
82
86
  return Object.freeze({ kind: 'refused', refusals: freeze(refusals) });
87
+ if (reasons.length > 0 && command === 'publish') {
88
+ return Object.freeze({
89
+ kind: 'refused',
90
+ refusals: freeze([
91
+ { reason: 'publish-legacy-direct', guidance: publishGuidance(environment, reasons) },
92
+ ]),
93
+ });
94
+ }
83
95
  if (reasons.length > 0)
84
96
  return Object.freeze({ kind: 'legacy-direct', reasons: freeze(reasons) });
85
97
  if (command === 'dev') {
@@ -97,6 +109,48 @@ export function selectDeployMode(input) {
97
109
  }
98
110
  return Object.freeze({ kind: 'canonical' });
99
111
  }
112
+ /**
113
+ * How an adapter-astrale Environment of the removed instance mode deploys now: without `instance`
114
+ * (nor `serviceKey` and `name`, which named its stable Service), outside development on a line its
115
+ * named organisation holds (AM-7), then installed by URL on the instance it used to deploy through.
116
+ */
117
+ function astraleInstanceGuidance(parameters, environment) {
118
+ const instance = String(parameters.instance);
119
+ const service = ASTRALE_SERVICE_PARAMETERS.filter((name) => declares(parameters, name));
120
+ const removed = service.length === 0
121
+ ? 'Remove `instance`'
122
+ : `Remove \`instance\` with ${service.map((name) => `\`${name}\``).join(' and ')}, ` +
123
+ `which named its stable Service`;
124
+ const organization = organizationGuidance(parameters, environment);
125
+ return (`astrale({ instance: '${instance}' }) deployed through that instance's Services, which no ` +
126
+ `longer hosts deployments. ${removed}${organization}: \`astrale-domain deploy ${environment}\` ` +
127
+ `makes an immutable deployment on Admin's Services, and \`${installCommand(instance)}\` ` +
128
+ 'installs it.');
129
+ }
130
+ /** Outside development, an adapter-astrale Environment names the organisation of its line (AM-7). */
131
+ function organizationGuidance(parameters, environment) {
132
+ return environment === DEVELOPMENT || declares(parameters, 'organization')
133
+ ? ''
134
+ : ", and name the organisation that holds the deployment line in `astrale({ organization: '<Identity id>' })`";
135
+ }
136
+ /**
137
+ * Why a legacy direct-mode Environment cannot be published, and the configuration that makes it
138
+ * publishable: a version names an immutable deployment's release, which a stable target never
139
+ * serves.
140
+ */
141
+ function publishGuidance(environment, reasons) {
142
+ const deploys = `\`astrale-domain publish ${environment}\` names an immutable deployment's release, and ${environment} deploys`;
143
+ if (reasons.some(({ reason }) => reason === 'no-deploy-release')) {
144
+ return (`${deploys} through an adapter without deployRelease, which replaces a stable target in ` +
145
+ 'place and serves no release. Use an adapter that implements deployRelease (adapter ' +
146
+ "interface v2), such as astrale() on Admin's Services.");
147
+ }
148
+ const direct = reasons.flatMap((reason) => reason.reason === 'direct-parameter' ? [reason.parameter] : []);
149
+ return (`${deploys} on cloudflare()'s legacy direct mode, which serves no release. Deploy it into a ` +
150
+ 'dispatch namespace with cloudflare({ namespace })' +
151
+ (direct.length === 0 ? '' : ` and remove ${direct.join(', ')}`) +
152
+ ", or on Admin's Services with astrale().");
153
+ }
100
154
  /** A parameter is declared when its value is not `undefined`, as optional adapter parameters read. */
101
155
  function declares(parameters, name) {
102
156
  return Object.hasOwn(parameters, name) && parameters[name] !== undefined;
@@ -0,0 +1,7 @@
1
+ import type { Version } from '../../../platform/versioning/index.js';
2
+ /**
3
+ * The version the author declares for the next release: `package.json` `version` in the Project
4
+ * directory, as release-please and `npm version` write it (tech Versioning [.21210]). No command
5
+ * takes a version number.
6
+ */
7
+ export declare function declaredVersion(projectDir: string): Promise<Version>;
@@ -0,0 +1,27 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { acceptVersion } from '../../../platform/versioning/index.js';
4
+ /**
5
+ * The version the author declares for the next release: `package.json` `version` in the Project
6
+ * directory, as release-please and `npm version` write it (tech Versioning [.21210]). No command
7
+ * takes a version number.
8
+ */
9
+ export async function declaredVersion(projectDir) {
10
+ const path = join(projectDir, 'package.json');
11
+ let manifest;
12
+ try {
13
+ manifest = JSON.parse(await readFile(path, 'utf8'));
14
+ }
15
+ catch (cause) {
16
+ throw new TypeError(`Cannot read the Domain's version: ${path} is not a readable JSON file.`, {
17
+ cause,
18
+ });
19
+ }
20
+ const version = typeof manifest === 'object' && manifest !== null && !Array.isArray(manifest)
21
+ ? manifest.version
22
+ : undefined;
23
+ if (typeof version !== 'string') {
24
+ throw new TypeError(`${path} declares no version. The Domain's version is the package.json version, for example written by npm version minor.`);
25
+ }
26
+ return acceptVersion(version);
27
+ }
@@ -0,0 +1,30 @@
1
+ import type { Build } from '../../../deployment/index.js';
2
+ import type { DomainRegistry } from '../registry/index.js';
3
+ export type { DiffChangeV1, DiffReportV1 } from './report.js';
4
+ export interface DiffCommandInput {
5
+ readonly projectDir: string;
6
+ readonly build: Build;
7
+ /** The build digest of every distinct bundler of the Project, bundled on demand. */
8
+ readonly builds: () => Promise<readonly string[]>;
9
+ readonly identity?: string;
10
+ readonly json: boolean;
11
+ readonly signal: AbortSignal;
12
+ }
13
+ export interface DiffCommandDependencies {
14
+ readonly open: (input: {
15
+ readonly projectDir: string;
16
+ readonly identity?: string;
17
+ readonly signal: AbortSignal;
18
+ }) => Promise<DomainRegistry>;
19
+ readonly stdout: (text: string) => void;
20
+ readonly stderr: (text: string) => void;
21
+ }
22
+ /**
23
+ * `astrale-domain diff [--as <identity>] [--json]`: the changes since the highest published stable
24
+ * version up to the declared one, and the smallest version they admit (tech Versioning [.72565]).
25
+ * On a feature branch the declared version is still the published one and the report is
26
+ * informational (exit 0); on a release PR an unpublished version that is too low fails (exit 1)
27
+ * with the [.105820] message. Reads the registry through the Astrale CLI under the caller's own
28
+ * credential and changes nothing.
29
+ */
30
+ export declare function runDiff(input: DiffCommandInput, dependencies?: DiffCommandDependencies): Promise<number>;
@@ -0,0 +1,58 @@
1
+ import * as bundle from '@astrale-os/kernel-dsl/v1/bundle/transport';
2
+ import { DIM, RED, RESET } from '../log.js';
3
+ import { RegistryCliError, openDomainRegistry } from '../registry/index.js';
4
+ import { declaredVersion } from './declared.js';
5
+ import { diffExitCode, renderDiff } from './render.js';
6
+ import { diffReport, publishedSchema } from './report.js';
7
+ /**
8
+ * `astrale-domain diff [--as <identity>] [--json]`: the changes since the highest published stable
9
+ * version up to the declared one, and the smallest version they admit (tech Versioning [.72565]).
10
+ * On a feature branch the declared version is still the published one and the report is
11
+ * informational (exit 0); on a release PR an unpublished version that is too low fails (exit 1)
12
+ * with the [.105820] message. Reads the registry through the Astrale CLI under the caller's own
13
+ * credential and changes nothing.
14
+ */
15
+ export async function runDiff(input, dependencies = {
16
+ open: openDomainRegistry,
17
+ stdout: (text) => void process.stdout.write(text),
18
+ stderr: (text) => void process.stderr.write(text),
19
+ }) {
20
+ const root = input.build.schema.compiled.root;
21
+ let report;
22
+ try {
23
+ const declared = await declaredVersion(input.projectDir);
24
+ const registry = await dependencies.open({
25
+ projectDir: input.projectDir,
26
+ ...(input.identity === undefined ? {} : { identity: input.identity }),
27
+ signal: input.signal,
28
+ });
29
+ report = await diffReport({
30
+ origin: root.origin,
31
+ declared,
32
+ revision: root.revision,
33
+ index: await registry.index(root.origin),
34
+ current: () => bundle.decode(input.build.schema.bundle.bytes()).root,
35
+ builds: input.builds,
36
+ published: async (publication) => publishedSchema(await registry.bundle(root.origin, publication), publication, root.origin),
37
+ });
38
+ }
39
+ catch (cause) {
40
+ if (!(cause instanceof RegistryCliError))
41
+ throw cause;
42
+ // Nothing on stdout: a JSON consumer reads one report or nothing.
43
+ dependencies.stderr(`${RED}✗${RESET} Domain registry: ${cause.code}: ${cause.message}\n`);
44
+ return 1;
45
+ }
46
+ const rendered = renderDiff(report);
47
+ // With --json, stdout carries only the report and the summary moves to stderr, as for deploy.
48
+ const summary = input.json ? dependencies.stderr : dependencies.stdout;
49
+ // The sentence takes the progress mark; the member rows below it keep their indentation.
50
+ for (const line of rendered.summary) {
51
+ summary(line.startsWith(' ') ? `${line}\n` : `${DIM}›${RESET} ${line}\n`);
52
+ }
53
+ if (rendered.refusal.length > 0)
54
+ dependencies.stderr(`${rendered.refusal.join('\n')}\n`);
55
+ if (input.json)
56
+ dependencies.stdout(`${JSON.stringify(report, null, 2)}\n`);
57
+ return diffExitCode(report);
58
+ }
@@ -0,0 +1,15 @@
1
+ import type { DiffReportV1 } from './report.js';
2
+ /**
3
+ * 0 when the declared version is accepted, already published (the report is then informational:
4
+ * a feature branch keeps the last published number until its release), or has nothing to compare
5
+ * with; 1 when an unpublished declared version is too low.
6
+ */
7
+ export declare function diffExitCode(report: DiffReportV1): 0 | 1;
8
+ /**
9
+ * The report for a person. `summary` describes the comparison; `refusal` is the [.105820] message
10
+ * for an unpublished version that is too low (tech Versioning), printed as an error.
11
+ */
12
+ export declare function renderDiff(report: DiffReportV1): {
13
+ readonly summary: readonly string[];
14
+ readonly refusal: readonly string[];
15
+ };