@noodleseed/one 0.153.0 → 0.154.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/app-purge-reconciliation-args.d.ts +28 -0
- package/dist/commands/app-purge-reconciliation-args.d.ts.map +1 -0
- package/dist/commands/app-purge-reconciliation-args.js +124 -0
- package/dist/commands/app-purge-reconciliation-args.js.map +1 -0
- package/dist/commands/app-purge-reconciliation-files.d.ts +8 -0
- package/dist/commands/app-purge-reconciliation-files.d.ts.map +1 -0
- package/dist/commands/app-purge-reconciliation-files.js +109 -0
- package/dist/commands/app-purge-reconciliation-files.js.map +1 -0
- package/dist/commands/app-purge-reconciliation-ops.d.ts +3 -0
- package/dist/commands/app-purge-reconciliation-ops.d.ts.map +1 -0
- package/dist/commands/app-purge-reconciliation-ops.js +190 -0
- package/dist/commands/app-purge-reconciliation-ops.js.map +1 -0
- package/dist/commands/billing-ops.d.ts.map +1 -1
- package/dist/commands/billing-ops.js +7 -0
- package/dist/commands/billing-ops.js.map +1 -1
- package/dist/commands/billing-org-transfer-files.d.ts +12 -0
- package/dist/commands/billing-org-transfer-files.d.ts.map +1 -0
- package/dist/commands/billing-org-transfer-files.js +112 -0
- package/dist/commands/billing-org-transfer-files.js.map +1 -0
- package/dist/commands/billing-org-transfer-ops.d.ts +4 -0
- package/dist/commands/billing-org-transfer-ops.d.ts.map +1 -0
- package/dist/commands/billing-org-transfer-ops.js +534 -0
- package/dist/commands/billing-org-transfer-ops.js.map +1 -0
- package/dist/commands/service-ops.d.ts.map +1 -1
- package/dist/commands/service-ops.js +4 -1
- package/dist/commands/service-ops.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-account.js +96 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-billing.js +116 -1
- package/node_modules/@noodle-borg/control-plane/dist/app-purge-reconciliation-port.d.ts +25 -0
- package/node_modules/@noodle-borg/control-plane/dist/app-purge-reconciliation-port.js +10 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +1 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/control-plane/package.json +3 -1
- package/node_modules/@noodle-borg/service/dist/routes/app-purge-reconciliation.js +72 -0
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +15 -2
- package/node_modules/@noodle-borg/service/dist/service.js +27 -1
- package/node_modules/@noodle-borg/service/dist/store/in-memory.js +4 -8
- package/node_modules/@noodle-borg/service/dist/store/json-file.js +6 -3
- package/node_modules/@noodle-borg/service/dist/store/records.js +20 -0
- package/node_modules/@noodle-borg/service/dist/store.d.ts +3 -2
- package/node_modules/@noodle-borg/service/package.json +1 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/app-purge-reconciliation.d.ts +177 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/app-purge-reconciliation.js +189 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/assistant.d.ts +1 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/billing-org-transfer.d.ts +629 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/billing-org-transfer.js +259 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.d.ts +21 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.js +23 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +2 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +2 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/platform-account-reset.d.ts +4 -4
- package/node_modules/@noodleseed/assistant/package.json +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { link, lstat, open, unlink } from 'node:fs/promises';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
import { BillingOrganizationTransferArtifactSchema, } from '@noodle-borg/wire-contracts';
|
|
6
|
+
const MAX_ARTIFACT_BYTES = 64 * 1024;
|
|
7
|
+
/** Refuse caller-controlled links and special files before a preview request is made. */
|
|
8
|
+
export async function assertBillingTransferArtifactDestination(path) {
|
|
9
|
+
try {
|
|
10
|
+
await lstat(path);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (isMissing(error))
|
|
14
|
+
return;
|
|
15
|
+
throw artifactFileError();
|
|
16
|
+
}
|
|
17
|
+
throw artifactFileError();
|
|
18
|
+
}
|
|
19
|
+
/** Install an already-synced private artifact without replacing a raced directory entry. */
|
|
20
|
+
export async function installBillingTransferArtifactNoReplace(temporaryPath, destinationPath) {
|
|
21
|
+
const directory = dirname(destinationPath);
|
|
22
|
+
if (dirname(temporaryPath) !== directory) {
|
|
23
|
+
await unlink(temporaryPath).catch(() => undefined);
|
|
24
|
+
throw artifactFileError();
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
await link(temporaryPath, destinationPath);
|
|
28
|
+
await unlink(temporaryPath);
|
|
29
|
+
const directoryHandle = await open(directory, constants.O_RDONLY);
|
|
30
|
+
try {
|
|
31
|
+
await directoryHandle.sync();
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
await directoryHandle.close();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
await unlink(temporaryPath).catch(() => undefined);
|
|
39
|
+
throw artifactFileError();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Write private reviewed evidence through a same-directory, exclusive temporary file. */
|
|
43
|
+
export async function writeBillingTransferArtifact(path, artifact) {
|
|
44
|
+
const validated = BillingOrganizationTransferArtifactSchema.parse(artifact);
|
|
45
|
+
const contents = `${JSON.stringify(validated, null, 2)}\n`;
|
|
46
|
+
if (Buffer.byteLength(contents, 'utf8') > MAX_ARTIFACT_BYTES)
|
|
47
|
+
throw artifactFileError();
|
|
48
|
+
await assertBillingTransferArtifactDestination(path);
|
|
49
|
+
const temporaryPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
50
|
+
let handle;
|
|
51
|
+
try {
|
|
52
|
+
handle = await open(temporaryPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
53
|
+
await handle.chmod(0o600);
|
|
54
|
+
await handle.writeFile(contents, 'utf8');
|
|
55
|
+
await handle.sync();
|
|
56
|
+
await handle.close();
|
|
57
|
+
handle = undefined;
|
|
58
|
+
await installBillingTransferArtifactNoReplace(temporaryPath, path);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw artifactFileError();
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
await handle?.close().catch(() => undefined);
|
|
65
|
+
await unlink(temporaryPath).catch(() => undefined);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Read strict private evidence from an opened descriptor without following a symlink. */
|
|
69
|
+
export async function readBillingTransferArtifact(path, authorityPath) {
|
|
70
|
+
let handle;
|
|
71
|
+
try {
|
|
72
|
+
handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
throw artifactFileError();
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const stat = await handle.stat();
|
|
79
|
+
if (!stat.isFile() || (stat.mode & 0o7777) !== 0o600 || stat.size > MAX_ARTIFACT_BYTES) {
|
|
80
|
+
throw artifactFileError();
|
|
81
|
+
}
|
|
82
|
+
const contents = await handle.readFile('utf8');
|
|
83
|
+
if (Buffer.byteLength(contents, 'utf8') > MAX_ARTIFACT_BYTES)
|
|
84
|
+
throw artifactFileError();
|
|
85
|
+
const parsed = BillingOrganizationTransferArtifactSchema.parse(JSON.parse(contents));
|
|
86
|
+
if (parsed.authorityPath !== authorityPath)
|
|
87
|
+
throw artifactFileError();
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
throw artifactFileError();
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
await handle.close();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
class BillingTransferArtifactFileError extends Error {
|
|
98
|
+
constructor() {
|
|
99
|
+
super('The billing transfer preview artifact is invalid.');
|
|
100
|
+
this.name = 'BillingTransferArtifactFileError';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function artifactFileError() {
|
|
104
|
+
return new BillingTransferArtifactFileError();
|
|
105
|
+
}
|
|
106
|
+
function isMissing(error) {
|
|
107
|
+
return (typeof error === 'object' &&
|
|
108
|
+
error !== null &&
|
|
109
|
+
'code' in error &&
|
|
110
|
+
error.code === 'ENOENT');
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=billing-org-transfer-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"billing-org-transfer-files.js","sourceRoot":"","sources":["../../src/commands/billing-org-transfer-files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BillingOrganizationTransferAuthorityPath } from '@noodle-borg/wire-contracts';
|
|
2
|
+
import { type ConfigLocation } from '../config.js';
|
|
3
|
+
export declare function runBillingOrganizationTransfer(authorityPath: BillingOrganizationTransferAuthorityPath, rest: readonly string[], env: NodeJS.ProcessEnv, home: ConfigLocation): Promise<number>;
|
|
4
|
+
//# sourceMappingURL=billing-org-transfer-ops.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"billing-org-transfer-ops.d.ts","sourceRoot":"","sources":["../../src/commands/billing-org-transfer-ops.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,wCAAwC,EAGzC,MAAM,6BAA6B,CAAC;AAarC,OAAO,EAAE,KAAK,cAAc,EAAiC,MAAM,cAAc,CAAC;AAyBlF,wBAAsB,8BAA8B,CAClD,aAAa,EAAE,wCAAwC,EACvD,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,MAAM,CAAC,CAiBjB"}
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { BILLING_ORGANIZATION_TRANSFER_ERROR_CODES, BillingAdministrationTransferApplyRequestSchema, BillingAdministrationTransferPreviewRequestSchema, BillingOrganizationTransferApplyClientResponseSchema, BillingOrganizationTransferApplyRequestSchema, BillingOrganizationTransferCandidatesClientResponseSchema, BillingOrganizationTransferCandidatesRequestSchema, BillingOrganizationTransferPreviewClientResponseSchema, BillingOrganizationTransferPreviewRequestSchema, normalizeBillingOrganizationTransferServiceOrigin, } from '@noodle-borg/wire-contracts';
|
|
2
|
+
import { readConfig, resolveServiceUrl } from '../config.js';
|
|
3
|
+
import { resolveControlPlaneToken, ServiceRequestError, serviceJson } from '../control-plane.js';
|
|
4
|
+
import { DEFAULT_SERVICE_URL } from '../deploy.js';
|
|
5
|
+
import { assertBillingTransferArtifactDestination, readBillingTransferArtifact, writeBillingTransferArtifact, } from './billing-org-transfer-files.js';
|
|
6
|
+
import { EXIT, printJsonOk } from './output.js';
|
|
7
|
+
import { authRequired, parseCommandFlags, printCliFailure } from './shared.js';
|
|
8
|
+
const STABLE_TRANSFER_CODES = new Set(BILLING_ORGANIZATION_TRANSFER_ERROR_CODES);
|
|
9
|
+
export async function runBillingOrganizationTransfer(authorityPath, rest, env, home) {
|
|
10
|
+
const action = rest[0];
|
|
11
|
+
const actionRest = rest.slice(1);
|
|
12
|
+
if (authorityPath === 'customer' && action === 'candidates') {
|
|
13
|
+
return runCandidates(actionRest, env, home);
|
|
14
|
+
}
|
|
15
|
+
if (action === 'preview')
|
|
16
|
+
return runPreview(authorityPath, actionRest, env, home);
|
|
17
|
+
if (action === 'apply')
|
|
18
|
+
return runApply(authorityPath, actionRest, env, home);
|
|
19
|
+
const command = commandName(authorityPath, action ?? '');
|
|
20
|
+
return printCliFailure(command, usageFailure(command, `${command} requires ${authorityPath === 'customer' ? 'candidates, preview, or apply' : 'preview or apply'}`), rest.includes('--json'));
|
|
21
|
+
}
|
|
22
|
+
async function runCandidates(rest, env, home) {
|
|
23
|
+
const command = 'billing org transfer candidates';
|
|
24
|
+
const parsedArgs = parseCandidatesArgs(rest, command);
|
|
25
|
+
if (!parsedArgs.ok)
|
|
26
|
+
return printCliFailure(command, parsedArgs.error, rest.includes('--json'));
|
|
27
|
+
const args = parsedArgs.args;
|
|
28
|
+
const request = BillingOrganizationTransferCandidatesRequestSchema.safeParse({
|
|
29
|
+
...(args.query === undefined ? {} : { query: args.query }),
|
|
30
|
+
...(args.cursor === undefined ? {} : { cursor: args.cursor }),
|
|
31
|
+
...(args.limit === undefined ? {} : { limit: Number(args.limit) }),
|
|
32
|
+
});
|
|
33
|
+
const destination = BillingOrganizationTransferPreviewRequestSchema.safeParse({
|
|
34
|
+
schemaVersion: 1,
|
|
35
|
+
destinationBillingAccountId: args.account,
|
|
36
|
+
});
|
|
37
|
+
if (!request.success || !destination.success) {
|
|
38
|
+
return printCliFailure(command, usageFailure(command, 'candidate arguments failed validation'), args.json);
|
|
39
|
+
}
|
|
40
|
+
const configuredOrigin = configuredServiceOrigin(args, env, home, command);
|
|
41
|
+
if (!configuredOrigin.ok)
|
|
42
|
+
return printCliFailure(command, configuredOrigin.error, args.json);
|
|
43
|
+
const resolved = await resolveControlPlaneToken(commonResolution(args, env, home));
|
|
44
|
+
if (resolved.token === undefined)
|
|
45
|
+
return printCliFailure(command, authRequired(), args.json);
|
|
46
|
+
const normalized = parsedServiceOrigin(resolved.serviceUrl, command);
|
|
47
|
+
if (!normalized.ok)
|
|
48
|
+
return printCliFailure(command, normalized.error, args.json);
|
|
49
|
+
const service = normalized.origin;
|
|
50
|
+
if (service !== configuredOrigin.origin) {
|
|
51
|
+
return printCliFailure(command, serviceOriginFailure(command), args.json);
|
|
52
|
+
}
|
|
53
|
+
const query = new URLSearchParams();
|
|
54
|
+
if (request.data.query !== undefined)
|
|
55
|
+
query.set('query', request.data.query);
|
|
56
|
+
if (request.data.cursor !== undefined)
|
|
57
|
+
query.set('cursor', request.data.cursor);
|
|
58
|
+
query.set('limit', String(request.data.limit));
|
|
59
|
+
const url = `${service}/v1/billing-accounts/${encodeURIComponent(destination.data.destinationBillingAccountId)}/organization-transfer-candidates?${query.toString()}`;
|
|
60
|
+
try {
|
|
61
|
+
const response = BillingOrganizationTransferCandidatesClientResponseSchema.parse(await serviceJson(url, resolved.token));
|
|
62
|
+
if (args.json)
|
|
63
|
+
printJsonOk({ service, candidates: response.data });
|
|
64
|
+
else
|
|
65
|
+
printCandidatePage(response.data);
|
|
66
|
+
return EXIT.OK;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return printCliFailure(command, transferServiceFailure(error, command), args.json);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function runPreview(authorityPath, rest, env, home) {
|
|
73
|
+
const command = commandName(authorityPath, 'preview');
|
|
74
|
+
const parsedArgs = parsePreviewArgs(authorityPath, rest, command);
|
|
75
|
+
if (!parsedArgs.ok)
|
|
76
|
+
return printCliFailure(command, parsedArgs.error, rest.includes('--json'));
|
|
77
|
+
const args = parsedArgs.args;
|
|
78
|
+
let request;
|
|
79
|
+
if (authorityPath === 'customer') {
|
|
80
|
+
const parsed = BillingAdministrationTransferPreviewRequestSchema.omit({
|
|
81
|
+
administrativeReason: true,
|
|
82
|
+
}).safeParse({
|
|
83
|
+
schemaVersion: 1,
|
|
84
|
+
organizationSlug: args.org,
|
|
85
|
+
destinationBillingAccountId: args.account,
|
|
86
|
+
});
|
|
87
|
+
if (!parsed.success) {
|
|
88
|
+
return printCliFailure(command, usageFailure(command, 'preview arguments failed validation'), args.json);
|
|
89
|
+
}
|
|
90
|
+
request = parsed.data;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
const parsed = BillingAdministrationTransferPreviewRequestSchema.safeParse({
|
|
94
|
+
schemaVersion: 1,
|
|
95
|
+
organizationSlug: args.org,
|
|
96
|
+
destinationBillingAccountId: args.account,
|
|
97
|
+
administrativeReason: args.reason,
|
|
98
|
+
});
|
|
99
|
+
if (!parsed.success) {
|
|
100
|
+
return printCliFailure(command, usageFailure(command, 'preview arguments failed validation'), args.json);
|
|
101
|
+
}
|
|
102
|
+
request = parsed.data;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
await assertBillingTransferArtifactDestination(args.out);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return printCliFailure(command, artifactDestinationFailure(args.out, privateInputs(args)), args.json);
|
|
109
|
+
}
|
|
110
|
+
const configuredOrigin = configuredServiceOrigin(args, env, home, command);
|
|
111
|
+
if (!configuredOrigin.ok)
|
|
112
|
+
return printCliFailure(command, configuredOrigin.error, args.json);
|
|
113
|
+
const resolved = await resolveControlPlaneToken(commonResolution(args, env, home));
|
|
114
|
+
if (resolved.token === undefined)
|
|
115
|
+
return printCliFailure(command, authRequired(), args.json);
|
|
116
|
+
const normalized = parsedServiceOrigin(resolved.serviceUrl, command);
|
|
117
|
+
if (!normalized.ok)
|
|
118
|
+
return printCliFailure(command, normalized.error, args.json);
|
|
119
|
+
const service = normalized.origin;
|
|
120
|
+
if (service !== configuredOrigin.origin) {
|
|
121
|
+
return printCliFailure(command, serviceOriginFailure(command), args.json);
|
|
122
|
+
}
|
|
123
|
+
const url = authorityPath === 'customer'
|
|
124
|
+
? `${service}/v1/orgs/${encodeURIComponent(args.org)}/billing-transfer/preview`
|
|
125
|
+
: `${service}/v1/billing-administration/organization-transfers/preview`;
|
|
126
|
+
try {
|
|
127
|
+
const serviceRequest = authorityPath === 'customer'
|
|
128
|
+
? BillingOrganizationTransferPreviewRequestSchema.parse({
|
|
129
|
+
schemaVersion: request.schemaVersion,
|
|
130
|
+
destinationBillingAccountId: request.destinationBillingAccountId,
|
|
131
|
+
})
|
|
132
|
+
: request;
|
|
133
|
+
const response = BillingOrganizationTransferPreviewClientResponseSchema.parse(await serviceJson(url, resolved.token, jsonPost(serviceRequest)));
|
|
134
|
+
const artifact = {
|
|
135
|
+
schemaVersion: 1,
|
|
136
|
+
serviceOrigin: service,
|
|
137
|
+
authorityPath,
|
|
138
|
+
request,
|
|
139
|
+
preview: response.data,
|
|
140
|
+
};
|
|
141
|
+
await writeBillingTransferArtifact(args.out, artifact);
|
|
142
|
+
if (args.json) {
|
|
143
|
+
printJsonOk({
|
|
144
|
+
service,
|
|
145
|
+
authority: authorityPath,
|
|
146
|
+
...(authorityPath === 'super_admin'
|
|
147
|
+
? {
|
|
148
|
+
administrativeReason: request
|
|
149
|
+
.administrativeReason,
|
|
150
|
+
}
|
|
151
|
+
: {}),
|
|
152
|
+
preview: response.data,
|
|
153
|
+
previewFile: args.out,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
printTransferPreview(response.data, args.out, authorityPath, authorityPath === 'super_admin'
|
|
158
|
+
? request.administrativeReason
|
|
159
|
+
: undefined);
|
|
160
|
+
}
|
|
161
|
+
return response.data.blockers.length === 0 ? EXIT.OK : EXIT.FAILURE;
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
const failure = error instanceof ServiceRequestError
|
|
165
|
+
? transferServiceFailure(error, command)
|
|
166
|
+
: artifactDestinationFailure(args.out, privateInputs(args));
|
|
167
|
+
return printCliFailure(command, failure, args.json);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function runApply(authorityPath, rest, env, home) {
|
|
171
|
+
const command = commandName(authorityPath, 'apply');
|
|
172
|
+
const parsedArgs = parseApplyArgs(rest, command);
|
|
173
|
+
if (!parsedArgs.ok)
|
|
174
|
+
return printCliFailure(command, parsedArgs.error, rest.includes('--json'));
|
|
175
|
+
const args = parsedArgs.args;
|
|
176
|
+
let artifact;
|
|
177
|
+
try {
|
|
178
|
+
artifact = await readBillingTransferArtifact(args.previewFile, authorityPath);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return printCliFailure(command, artifactFailure(args.previewFile, [args.idempotencyKey]), args.json);
|
|
182
|
+
}
|
|
183
|
+
if (artifact.preview.blockers.length > 0) {
|
|
184
|
+
return printCliFailure(command, blockedArtifactFailure(args.previewFile, privateInputs(args, artifact)), args.json);
|
|
185
|
+
}
|
|
186
|
+
const additions = {
|
|
187
|
+
expectedLinkVersion: artifact.preview.expectedLinkVersion,
|
|
188
|
+
previewChecksum: artifact.preview.previewChecksum,
|
|
189
|
+
idempotencyKey: args.idempotencyKey,
|
|
190
|
+
confirmed: true,
|
|
191
|
+
};
|
|
192
|
+
const parsedRequest = authorityPath === 'customer'
|
|
193
|
+
? BillingOrganizationTransferApplyRequestSchema.safeParse({
|
|
194
|
+
schemaVersion: artifact.request.schemaVersion,
|
|
195
|
+
destinationBillingAccountId: artifact.request.destinationBillingAccountId,
|
|
196
|
+
...additions,
|
|
197
|
+
})
|
|
198
|
+
: BillingAdministrationTransferApplyRequestSchema.safeParse({
|
|
199
|
+
...artifact.request,
|
|
200
|
+
...additions,
|
|
201
|
+
});
|
|
202
|
+
if (!parsedRequest.success) {
|
|
203
|
+
return printCliFailure(command, artifactFailure(args.previewFile, privateInputs(args, artifact)), args.json);
|
|
204
|
+
}
|
|
205
|
+
const configuredOrigin = configuredServiceOrigin(args, env, home, command);
|
|
206
|
+
if (!configuredOrigin.ok)
|
|
207
|
+
return printCliFailure(command, configuredOrigin.error, args.json);
|
|
208
|
+
if (configuredOrigin.origin !== artifact.serviceOrigin) {
|
|
209
|
+
return printCliFailure(command, artifactFailure(args.previewFile, privateInputs(args, artifact)), args.json);
|
|
210
|
+
}
|
|
211
|
+
const resolved = await resolveControlPlaneToken(commonResolution(args, env, home));
|
|
212
|
+
const normalized = parsedServiceOrigin(resolved.serviceUrl, command);
|
|
213
|
+
if (!normalized.ok)
|
|
214
|
+
return printCliFailure(command, normalized.error, args.json);
|
|
215
|
+
const currentService = normalized.origin;
|
|
216
|
+
if (currentService !== artifact.serviceOrigin) {
|
|
217
|
+
return printCliFailure(command, artifactFailure(args.previewFile, privateInputs(args, artifact)), args.json);
|
|
218
|
+
}
|
|
219
|
+
if (resolved.token === undefined)
|
|
220
|
+
return printCliFailure(command, authRequired(), args.json);
|
|
221
|
+
const url = authorityPath === 'customer'
|
|
222
|
+
? `${artifact.serviceOrigin}/v1/orgs/${encodeURIComponent(artifact.request.organizationSlug)}/billing-transfer`
|
|
223
|
+
: `${artifact.serviceOrigin}/v1/billing-administration/organization-transfers`;
|
|
224
|
+
try {
|
|
225
|
+
const response = BillingOrganizationTransferApplyClientResponseSchema.parse(await serviceJson(url, resolved.token, jsonPost(parsedRequest.data)));
|
|
226
|
+
if (args.json)
|
|
227
|
+
printJsonOk({
|
|
228
|
+
service: artifact.serviceOrigin,
|
|
229
|
+
authority: authorityPath,
|
|
230
|
+
transfer: response.data,
|
|
231
|
+
});
|
|
232
|
+
else
|
|
233
|
+
printTransferResult(response.data, authorityPath);
|
|
234
|
+
return EXIT.OK;
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
return printCliFailure(command, transferServiceFailure(error, command), args.json);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function parseCandidatesArgs(rest, command) {
|
|
241
|
+
const parsed = parseCommandFlags(rest, {
|
|
242
|
+
values: {
|
|
243
|
+
'--account': 'account',
|
|
244
|
+
'--query': 'query',
|
|
245
|
+
'--cursor': 'cursor',
|
|
246
|
+
'--limit': 'limit',
|
|
247
|
+
'--service': 'service',
|
|
248
|
+
'--auth-token': 'authToken',
|
|
249
|
+
},
|
|
250
|
+
booleans: { '--json': 'json' },
|
|
251
|
+
});
|
|
252
|
+
if (parsed.parseError !== undefined)
|
|
253
|
+
return failedUsage(command, parsed.parseError);
|
|
254
|
+
const duplicate = duplicateFlag(rest);
|
|
255
|
+
if (duplicate !== undefined)
|
|
256
|
+
return failedUsage(command, `${duplicate} may be supplied once`);
|
|
257
|
+
if (parsed.positional.length > 0)
|
|
258
|
+
return failedUsage(command, 'candidates accepts no positional arguments');
|
|
259
|
+
if (parsed.account === undefined)
|
|
260
|
+
return failedUsage(command, '--account is required');
|
|
261
|
+
return {
|
|
262
|
+
ok: true,
|
|
263
|
+
args: {
|
|
264
|
+
account: parsed.account,
|
|
265
|
+
...commonArgs(parsed),
|
|
266
|
+
...(parsed.query === undefined ? {} : { query: parsed.query }),
|
|
267
|
+
...(parsed.cursor === undefined ? {} : { cursor: parsed.cursor }),
|
|
268
|
+
...(parsed.limit === undefined ? {} : { limit: parsed.limit }),
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function parsePreviewArgs(authorityPath, rest, command) {
|
|
273
|
+
const parsed = parseCommandFlags(rest, {
|
|
274
|
+
values: {
|
|
275
|
+
'--org': 'org',
|
|
276
|
+
'--account': 'account',
|
|
277
|
+
'--reason': 'reason',
|
|
278
|
+
'--out': 'out',
|
|
279
|
+
'--service': 'service',
|
|
280
|
+
'--auth-token': 'authToken',
|
|
281
|
+
},
|
|
282
|
+
booleans: { '--json': 'json' },
|
|
283
|
+
});
|
|
284
|
+
if (parsed.parseError !== undefined)
|
|
285
|
+
return failedUsage(command, parsed.parseError);
|
|
286
|
+
const duplicate = duplicateFlag(rest);
|
|
287
|
+
if (duplicate !== undefined)
|
|
288
|
+
return failedUsage(command, `${duplicate} may be supplied once`);
|
|
289
|
+
if (authorityPath === 'customer' && (parsed.org !== undefined || parsed.reason !== undefined))
|
|
290
|
+
return failedUsage(command, 'unknown customer preview option');
|
|
291
|
+
const org = authorityPath === 'customer' ? parsed.positional[0] : parsed.org;
|
|
292
|
+
if (parsed.account === undefined)
|
|
293
|
+
return failedUsage(command, '--account is required');
|
|
294
|
+
if (parsed.out === undefined)
|
|
295
|
+
return failedUsage(command, '--out is required');
|
|
296
|
+
if (org === undefined)
|
|
297
|
+
return failedUsage(command, authorityPath === 'customer' ? 'one organization slug is required' : '--org is required');
|
|
298
|
+
if (authorityPath === 'customer' && parsed.positional.length !== 1)
|
|
299
|
+
return failedUsage(command, 'exactly one organization slug is required');
|
|
300
|
+
if (authorityPath === 'super_admin' && parsed.positional.length > 0)
|
|
301
|
+
return failedUsage(command, 'administrator preview accepts no positional arguments');
|
|
302
|
+
if (authorityPath === 'super_admin' && parsed.reason === undefined)
|
|
303
|
+
return failedUsage(command, '--reason is required');
|
|
304
|
+
return {
|
|
305
|
+
ok: true,
|
|
306
|
+
args: {
|
|
307
|
+
org,
|
|
308
|
+
account: parsed.account,
|
|
309
|
+
out: parsed.out,
|
|
310
|
+
...(parsed.reason === undefined ? {} : { reason: parsed.reason }),
|
|
311
|
+
...commonArgs(parsed),
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function parseApplyArgs(rest, command) {
|
|
316
|
+
const parsed = parseCommandFlags(rest, {
|
|
317
|
+
values: {
|
|
318
|
+
'--preview-file': 'previewFile',
|
|
319
|
+
'--idempotency-key': 'idempotencyKey',
|
|
320
|
+
'--service': 'service',
|
|
321
|
+
'--auth-token': 'authToken',
|
|
322
|
+
},
|
|
323
|
+
booleans: { '--yes': 'yes', '--json': 'json' },
|
|
324
|
+
});
|
|
325
|
+
if (parsed.parseError !== undefined)
|
|
326
|
+
return failedUsage(command, parsed.parseError);
|
|
327
|
+
const duplicate = duplicateFlag(rest);
|
|
328
|
+
if (duplicate !== undefined)
|
|
329
|
+
return failedUsage(command, `${duplicate} may be supplied once`);
|
|
330
|
+
if (parsed.positional.length > 0)
|
|
331
|
+
return failedUsage(command, 'apply accepts no positional arguments');
|
|
332
|
+
if (parsed.previewFile === undefined)
|
|
333
|
+
return failedUsage(command, '--preview-file is required');
|
|
334
|
+
if (parsed.idempotencyKey === undefined)
|
|
335
|
+
return failedUsage(command, '--idempotency-key is required');
|
|
336
|
+
if (!parsed.yes)
|
|
337
|
+
return failedUsage(command, '--yes is required');
|
|
338
|
+
return {
|
|
339
|
+
ok: true,
|
|
340
|
+
args: {
|
|
341
|
+
previewFile: parsed.previewFile,
|
|
342
|
+
idempotencyKey: parsed.idempotencyKey,
|
|
343
|
+
...commonArgs(parsed),
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function duplicateFlag(rest) {
|
|
348
|
+
const flags = rest.filter((argument) => argument.startsWith('--'));
|
|
349
|
+
return flags.find((flag, index) => flags.indexOf(flag) !== index);
|
|
350
|
+
}
|
|
351
|
+
function commonArgs(parsed) {
|
|
352
|
+
return {
|
|
353
|
+
...(parsed.service === undefined ? {} : { service: parsed.service }),
|
|
354
|
+
...(parsed.authToken === undefined ? {} : { authToken: parsed.authToken }),
|
|
355
|
+
json: parsed.json,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function configuredServiceOrigin(args, env, home, command) {
|
|
359
|
+
try {
|
|
360
|
+
return parsedServiceOrigin(resolveServiceUrl(args.service, env, readConfig(home)) ?? DEFAULT_SERVICE_URL, command);
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
return { ok: false, error: serviceOriginFailure(command) };
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function parsedServiceOrigin(value, command) {
|
|
367
|
+
try {
|
|
368
|
+
return { ok: true, origin: normalizeBillingOrganizationTransferServiceOrigin(value) };
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
return { ok: false, error: serviceOriginFailure(command) };
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function commonResolution(args, env, home) {
|
|
375
|
+
return { serviceFlag: args.service, authFlag: args.authToken, env, home };
|
|
376
|
+
}
|
|
377
|
+
function jsonPost(body) {
|
|
378
|
+
return {
|
|
379
|
+
method: 'POST',
|
|
380
|
+
headers: { 'content-type': 'application/json' },
|
|
381
|
+
body: JSON.stringify(body),
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function printCandidatePage(page) {
|
|
385
|
+
console.log(`Billing organization transfer candidates: ${page.items.length}`);
|
|
386
|
+
if (page.items.length === 0)
|
|
387
|
+
console.log('No owned organizations matched this request.');
|
|
388
|
+
for (const candidate of page.items) {
|
|
389
|
+
console.log(`${candidate.organization.displayName} (${candidate.organization.slug}) — ${candidate.state}`);
|
|
390
|
+
printCodes('Warnings', candidate.warnings);
|
|
391
|
+
printCodes('Blockers', candidate.blockers);
|
|
392
|
+
}
|
|
393
|
+
console.log(`Next cursor: ${page.nextCursor ?? 'none'}`);
|
|
394
|
+
}
|
|
395
|
+
function printTransferPreview(preview, file, authorityPath, administrativeReason) {
|
|
396
|
+
console.log(`Billing organization transfer preview: ${preview.blockers.length === 0 ? 'READY' : 'BLOCKED'}`);
|
|
397
|
+
console.log(`Authority: ${authorityPath === 'customer' ? 'CUSTOMER OWNER' : 'SUPER ADMIN'}`);
|
|
398
|
+
if (administrativeReason !== undefined)
|
|
399
|
+
console.log(`Administrative reason: ${administrativeReason}`);
|
|
400
|
+
console.log(`Organization: ${preview.organization.displayName} (${preview.organization.slug})`);
|
|
401
|
+
console.log(`Destination: ${preview.destination.displayName} (${preview.destination.id})`);
|
|
402
|
+
console.log(`Plan: ${preview.currentPlan?.code ?? 'unavailable'} → ${preview.resultingPlan?.code ?? 'unavailable'}`);
|
|
403
|
+
console.log(`Production apps: ${preview.productionCapacity.prospective}/${preview.productionCapacity.limit} after move`);
|
|
404
|
+
console.log(`Fallback: ${preview.fallback}`);
|
|
405
|
+
printCodes('Warnings', preview.warnings);
|
|
406
|
+
printCodes('Blockers', preview.blockers);
|
|
407
|
+
console.log(`Reviewed preview: ${file}`);
|
|
408
|
+
console.log('No changes were made.');
|
|
409
|
+
}
|
|
410
|
+
function printTransferResult(result, authorityPath) {
|
|
411
|
+
console.log(`Billing organization transfer: ${result.replayed ? 'ALREADY APPLIED' : 'APPLIED'}`);
|
|
412
|
+
console.log(`Authority: ${authorityPath === 'customer' ? 'CUSTOMER OWNER' : 'SUPER ADMIN'}`);
|
|
413
|
+
console.log(`Operation ID: ${result.operationId}`);
|
|
414
|
+
console.log(`Organization: ${result.organization.displayName} (${result.organization.slug})`);
|
|
415
|
+
console.log(`Destination: ${result.destination.displayName} (${result.destination.id})`);
|
|
416
|
+
console.log(`Resulting plan: ${result.resultingPlan.code}`);
|
|
417
|
+
console.log(`Production apps: ${result.productionCapacity.prospective}/${result.productionCapacity.limit}`);
|
|
418
|
+
console.log(`Effective: ${result.effectiveAt}`);
|
|
419
|
+
console.log(`Audit recorded: ${result.recordedAt}`);
|
|
420
|
+
printCodes('Warnings', result.warnings);
|
|
421
|
+
if (result.replayed)
|
|
422
|
+
console.log('The exact stored result was returned; no duplicate transfer was made.');
|
|
423
|
+
}
|
|
424
|
+
function printCodes(label, codes) {
|
|
425
|
+
console.log(`${label}: ${codes.length}`);
|
|
426
|
+
for (const code of codes)
|
|
427
|
+
console.log(` [${code}]`);
|
|
428
|
+
}
|
|
429
|
+
function commandName(authorityPath, action) {
|
|
430
|
+
return authorityPath === 'customer'
|
|
431
|
+
? `billing org transfer${action.length > 0 ? ` ${action}` : ''}`
|
|
432
|
+
: `billing administration transfer${action.length > 0 ? ` ${action}` : ''}`;
|
|
433
|
+
}
|
|
434
|
+
function usageFailure(command, message) {
|
|
435
|
+
return {
|
|
436
|
+
code: 'usage_error',
|
|
437
|
+
message,
|
|
438
|
+
cause: 'The billing organization transfer arguments are invalid.',
|
|
439
|
+
fix: 'Supply every required exact-review input.',
|
|
440
|
+
next: `noodle ${command} --help`,
|
|
441
|
+
exitCode: EXIT.USAGE,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function failedUsage(command, message) {
|
|
445
|
+
return { ok: false, error: usageFailure(command, message) };
|
|
446
|
+
}
|
|
447
|
+
function artifactFailure(file, privateValues = []) {
|
|
448
|
+
const location = redact(file, privateValues);
|
|
449
|
+
return {
|
|
450
|
+
code: 'invalid_billing_transfer_preview_file',
|
|
451
|
+
message: `Cannot use billing organization transfer preview file ${location}.`,
|
|
452
|
+
cause: 'The private preview file is missing, unsafe, malformed, mismatched, or no longer exact.',
|
|
453
|
+
fix: 'Capture a fresh private preview from the same service and authority path; do not edit it.',
|
|
454
|
+
next: 'Run the matching billing organization transfer preview command again.',
|
|
455
|
+
exitCode: EXIT.FAILURE,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
function artifactDestinationFailure(file, privateValues) {
|
|
459
|
+
const location = redact(file, privateValues);
|
|
460
|
+
return {
|
|
461
|
+
code: 'invalid_billing_transfer_preview_file',
|
|
462
|
+
message: `Cannot write billing organization transfer preview file ${location}.`,
|
|
463
|
+
cause: 'The output path is unsafe, unavailable, or already exists.',
|
|
464
|
+
fix: 'Choose a new output path; reviewed preview artifacts are never overwritten.',
|
|
465
|
+
next: 'Run the matching billing organization transfer preview command with a new --out path.',
|
|
466
|
+
exitCode: EXIT.FAILURE,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function blockedArtifactFailure(file, privateValues) {
|
|
470
|
+
const location = redact(file, privateValues);
|
|
471
|
+
return {
|
|
472
|
+
code: 'billing_transfer_preview_blocked',
|
|
473
|
+
message: `The reviewed billing organization transfer preview at ${location} is blocked.`,
|
|
474
|
+
cause: 'The saved preview contains one or more blockers.',
|
|
475
|
+
fix: 'Resolve every blocker and capture a fresh preview.',
|
|
476
|
+
next: 'Run the matching billing organization transfer preview command again.',
|
|
477
|
+
exitCode: EXIT.FAILURE,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function serviceOriginFailure(command) {
|
|
481
|
+
return {
|
|
482
|
+
code: 'invalid_service_origin',
|
|
483
|
+
message: 'The billing organization transfer service must be one HTTP(S) origin.',
|
|
484
|
+
cause: 'The configured service includes an invalid URL, credentials, path, query, or fragment.',
|
|
485
|
+
fix: 'Use only the canonical control-plane origin.',
|
|
486
|
+
next: `noodle ${command} --service <https-origin>`,
|
|
487
|
+
exitCode: EXIT.USAGE,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function transferServiceFailure(error, command) {
|
|
491
|
+
const service = error instanceof ServiceRequestError ? error : undefined;
|
|
492
|
+
const auth = service?.status === 401 || service?.status === 403;
|
|
493
|
+
const unreachable = service?.status === 0;
|
|
494
|
+
const code = service?.code !== undefined && STABLE_TRANSFER_CODES.has(service.code)
|
|
495
|
+
? service.code
|
|
496
|
+
: auth
|
|
497
|
+
? 'auth_failed'
|
|
498
|
+
: unreachable
|
|
499
|
+
? 'service_unreachable'
|
|
500
|
+
: 'service_error';
|
|
501
|
+
return {
|
|
502
|
+
code,
|
|
503
|
+
message: auth
|
|
504
|
+
? 'The billing organization transfer request was not authorized.'
|
|
505
|
+
: unreachable
|
|
506
|
+
? 'The billing organization transfer service could not be reached.'
|
|
507
|
+
: 'The billing organization transfer request failed.',
|
|
508
|
+
cause: auth
|
|
509
|
+
? 'The current identity is not authorized for this transfer authority path.'
|
|
510
|
+
: unreachable
|
|
511
|
+
? 'The service outcome is uncertain; preserve the reviewed preview file and private idempotency key.'
|
|
512
|
+
: 'The service rejected the request without exposing private billing details.',
|
|
513
|
+
fix: unreachable
|
|
514
|
+
? 'Retry the exact apply with the same preview file and private idempotency key.'
|
|
515
|
+
: 'Check the typed failure and capture a fresh preview when required.',
|
|
516
|
+
next: unreachable
|
|
517
|
+
? `noodle ${command} --preview-file <same-reviewed-preview.json> --idempotency-key <same-private-key> --yes`
|
|
518
|
+
: `noodle ${command} --help`,
|
|
519
|
+
exitCode: auth ? EXIT.AUTH : unreachable ? EXIT.UNREACHABLE : EXIT.FAILURE,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
function privateInputs(args, artifact) {
|
|
523
|
+
return [
|
|
524
|
+
args.reason,
|
|
525
|
+
args.idempotencyKey,
|
|
526
|
+
artifact?.authorityPath === 'super_admin' ? artifact.request.administrativeReason : undefined,
|
|
527
|
+
];
|
|
528
|
+
}
|
|
529
|
+
function redact(value, privateValues) {
|
|
530
|
+
return privateValues.reduce((safe, privateValue) => privateValue === undefined || privateValue.length === 0
|
|
531
|
+
? safe
|
|
532
|
+
: safe.split(privateValue).join('[redacted]'), value);
|
|
533
|
+
}
|
|
534
|
+
//# sourceMappingURL=billing-org-transfer-ops.js.map
|