orizu 0.5.52 → 0.5.54
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/app-commands.d.ts +2 -1
- package/dist/app-commands.d.ts.map +1 -1
- package/dist/app-commands.js +27 -20
- package/dist/auth-commands.js +2 -2
- package/dist/cli-console-output.d.ts +7 -1
- package/dist/cli-console-output.d.ts.map +1 -1
- package/dist/cli-console-output.js +16 -3
- package/dist/cli-stops.d.ts +14 -0
- package/dist/cli-stops.d.ts.map +1 -0
- package/dist/cli-stops.js +22 -0
- package/dist/egress-allowlist-cli.d.ts +29 -2
- package/dist/egress-allowlist-cli.d.ts.map +1 -1
- package/dist/egress-allowlist-cli.js +16 -89
- package/dist/egress-allowlist-workflow.d.ts +17 -0
- package/dist/egress-allowlist-workflow.d.ts.map +1 -0
- package/dist/egress-allowlist-workflow.js +183 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/instruction-delivery.d.ts +4 -4
- package/dist/instruction-delivery.d.ts.map +1 -1
- package/dist/instruction-delivery.js +32 -46
- package/dist/instruction-prune.d.ts +24 -0
- package/dist/instruction-prune.d.ts.map +1 -0
- package/dist/instruction-prune.js +33 -0
- package/dist/instruction-set-sync/index.d.ts +0 -2
- package/dist/instruction-set-sync/index.d.ts.map +1 -1
- package/dist/instruction-set-sync/index.js +3 -36
- package/dist/instruction-set-update/index.d.ts +41 -3
- package/dist/instruction-set-update/index.d.ts.map +1 -1
- package/dist/instruction-set-update/index.js +156 -132
- package/dist/instruction-sets-cli.d.ts.map +1 -1
- package/dist/instruction-sets-cli.js +21 -2
- package/dist/instruction-sync-lock.d.ts +9 -0
- package/dist/instruction-sync-lock.d.ts.map +1 -0
- package/dist/instruction-sync-lock.js +24 -0
- package/dist/json-output-scrub.d.ts +3 -0
- package/dist/json-output-scrub.d.ts.map +1 -0
- package/dist/json-output-scrub.js +70 -0
- package/dist/one-request-write.d.ts.map +1 -1
- package/dist/one-request-write.js +2 -9
- package/dist/preview-runtime.d.ts +14 -3
- package/dist/preview-runtime.d.ts.map +1 -1
- package/dist/preview-runtime.js +13 -255
- package/dist/preview-workflow.d.ts +11 -0
- package/dist/preview-workflow.d.ts.map +1 -0
- package/dist/preview-workflow.js +260 -0
- package/dist/workbench-cli.d.ts +2 -1
- package/dist/workbench-cli.d.ts.map +1 -1
- package/dist/workbench-cli.js +79 -593
- package/dist/workbench-workflows.d.ts +56 -0
- package/dist/workbench-workflows.d.ts.map +1 -0
- package/dist/workbench-workflows.js +665 -0
- package/dist/workspace-sync-workflow.d.ts +115 -0
- package/dist/workspace-sync-workflow.d.ts.map +1 -0
- package/dist/workspace-sync-workflow.js +642 -0
- package/dist/workspace-sync.d.ts +45 -97
- package/dist/workspace-sync.d.ts.map +1 -1
- package/dist/workspace-sync.js +53 -494
- package/package.json +1 -1
- package/src/app-commands.ts +30 -22
- package/src/auth-commands.ts +2 -2
- package/src/cli-console-output.ts +16 -2
- package/src/cli-stops.ts +27 -0
- package/src/egress-allowlist-cli.ts +18 -90
- package/src/egress-allowlist-workflow.ts +198 -0
- package/src/index.ts +7 -1
- package/src/instruction-delivery.ts +31 -47
- package/src/instruction-prune.ts +59 -0
- package/src/instruction-set-sync/index.ts +3 -43
- package/src/instruction-set-update/index.ts +173 -122
- package/src/instruction-sets-cli.ts +23 -2
- package/src/instruction-sync-lock.ts +27 -0
- package/src/json-output-scrub.ts +73 -0
- package/src/one-request-write.ts +2 -10
- package/src/preview-runtime.ts +18 -280
- package/src/preview-workflow.ts +313 -0
- package/src/workbench-cli.ts +83 -684
- package/src/workbench-workflows.ts +815 -0
- package/src/workspace-sync-workflow.ts +838 -0
- package/src/workspace-sync.ts +63 -630
package/dist/app-commands.d.ts
CHANGED
|
@@ -20,7 +20,8 @@ declare function readPromptSidecars(manifest: Record<string, unknown>, promptDir
|
|
|
20
20
|
declare function writeTextFileEnsuringDir(pathArg: string, content: string): void;
|
|
21
21
|
declare function readManifestFile(dirArg: string): Record<string, unknown>;
|
|
22
22
|
declare function createAppFromFile(): Promise<void>;
|
|
23
|
-
|
|
23
|
+
/** Resolves to the exit code of a stop (130 or 143), or undefined when the preview ended normally. */
|
|
24
|
+
declare function previewAppFromFile(): Promise<number | undefined>;
|
|
24
25
|
declare function updateAppFromFile(): Promise<void>;
|
|
25
26
|
declare function linkAppDataset(): Promise<void>;
|
|
26
27
|
declare function appDetail(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-commands.d.ts","sourceRoot":"","sources":["../src/app-commands.ts"],"names":[],"mappings":"AAuCA,iBAAe,QAAQ,kBActB;AAED,iBAAS,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAU/C;AAED,iBAAS,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAUhD;AAED,iBAAS,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE9D;AAeD,iBAAS,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAElE;AAED,iBAAS,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG1F;AAED,iBAAS,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUjE;AAED,iBAAS,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAEjD;AAED,iBAAS,qBAAqB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG;IACpF,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb,CAeA;AAED,iBAAS,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM;;;;;IAoB/E;AAED,iBAAS,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAGjE;AAED,iBAAS,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEjE;AAyED,iBAAe,iBAAiB,kBAmG/B;AAED,iBAAe,kBAAkB,
|
|
1
|
+
{"version":3,"file":"app-commands.d.ts","sourceRoot":"","sources":["../src/app-commands.ts"],"names":[],"mappings":"AAuCA,iBAAe,QAAQ,kBActB;AAED,iBAAS,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAU/C;AAED,iBAAS,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAUhD;AAED,iBAAS,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE9D;AAeD,iBAAS,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAElE;AAED,iBAAS,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG1F;AAED,iBAAS,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUjE;AAED,iBAAS,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAEjD;AAED,iBAAS,qBAAqB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG;IACpF,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb,CAeA;AAED,iBAAS,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM;;;;;IAoB/E;AAED,iBAAS,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAGjE;AAED,iBAAS,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEjE;AAyED,iBAAe,iBAAiB,kBAmG/B;AAED,sGAAsG;AACtG,iBAAe,kBAAkB,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAwD/D;AAED,iBAAe,iBAAiB,kBAwF/B;AAED,iBAAe,cAAc,kBAiC5B;AA+CD,iBAAe,SAAS,kBA2CvB;AAED,yFAAyF;AACzF,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,CAK3G;AAED,iBAAe,eAAe,kBAmD7B;AAED,OAAO,EACL,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAChB,QAAQ,EACR,gBAAgB,EAChB,wBAAwB,EACxB,SAAS,EACT,eAAe,EACf,QAAQ,EACR,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,SAAS,EACT,eAAe,GAChB,CAAA"}
|
package/dist/app-commands.js
CHANGED
|
@@ -13,7 +13,6 @@ import { inspectAppDestination } from './app-publish-paths.js';
|
|
|
13
13
|
import { authedFetch, getBaseUrl } from './http.js';
|
|
14
14
|
import { appPageUrl } from './app-page-url.js';
|
|
15
15
|
import { parseJsonResponse, sanitizeHumanInlineText, sanitizeTerminalText } from './json-response.js';
|
|
16
|
-
import { runLocalAppPreview } from './preview-runtime.js';
|
|
17
16
|
import { findWorkspaceRoot } from './workspace.js';
|
|
18
17
|
async function listApps() {
|
|
19
18
|
const project = getArg('--project') || await resolveProjectSlug(null);
|
|
@@ -289,6 +288,7 @@ async function createAppFromFile() {
|
|
|
289
288
|
throw new Error(`${getErrorMessage(error)}\nApp ID: ${appId}\nFolder: ${recoveryFolder}\nRetry: ${retryArgs.map(shellSingleQuote).join(' ')}`);
|
|
290
289
|
}
|
|
291
290
|
}
|
|
291
|
+
/** Resolves to the exit code of a stop (130 or 143), or undefined when the preview ended normally. */
|
|
292
292
|
async function previewAppFromFile() {
|
|
293
293
|
const filePath = getArg('--file');
|
|
294
294
|
const inputSchemaPath = getArg('--input-schema');
|
|
@@ -306,7 +306,29 @@ async function previewAppFromFile() {
|
|
|
306
306
|
const outputJsonSchema = readJsonFile(outputSchemaPath);
|
|
307
307
|
const sampleRow = readJsonFile(sampleRowPath);
|
|
308
308
|
const expandedScreenshotPath = screenshotPath ? expandHomePath(screenshotPath) : undefined;
|
|
309
|
-
const
|
|
309
|
+
const printRendered = (result) => {
|
|
310
|
+
if (hasJsonFlag()) {
|
|
311
|
+
printJson({
|
|
312
|
+
url: result.url,
|
|
313
|
+
screenshotPath: result.screenshotPath || null,
|
|
314
|
+
warnings: result.warnings,
|
|
315
|
+
});
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
printLine(`Preview rendered: ${formatTerminalLink(result.url)}`);
|
|
319
|
+
if (result.screenshotPath) {
|
|
320
|
+
printLine(`Screenshot: ${sanitizeTerminalText(result.screenshotPath)}`);
|
|
321
|
+
}
|
|
322
|
+
if (headed || keepOpen) {
|
|
323
|
+
printLine('Headed preview is running. Close Chromium or stop the command when you are done.');
|
|
324
|
+
}
|
|
325
|
+
if (result.warnings.length > 0) {
|
|
326
|
+
printLine(`Warnings: ${sanitizeTerminalText(result.warnings.join('; '))}`);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
const { runPreviewCommand } = await import('./preview-workflow.js');
|
|
330
|
+
// Printed once the page is ready, so a keep-open preview says so while it is open.
|
|
331
|
+
const outcome = await runPreviewCommand({
|
|
310
332
|
filePath: expandedFilePath,
|
|
311
333
|
inputSchema: inputJsonSchema,
|
|
312
334
|
outputSchema: outputJsonSchema,
|
|
@@ -315,25 +337,10 @@ async function previewAppFromFile() {
|
|
|
315
337
|
componentName: component,
|
|
316
338
|
headed,
|
|
317
339
|
keepOpen,
|
|
340
|
+
onRendered: printRendered,
|
|
318
341
|
});
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
url: result.url,
|
|
322
|
-
screenshotPath: result.screenshotPath || null,
|
|
323
|
-
warnings: result.warnings,
|
|
324
|
-
});
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
printLine(`Preview rendered: ${formatTerminalLink(result.url)}`);
|
|
328
|
-
if (result.screenshotPath) {
|
|
329
|
-
printLine(`Screenshot: ${sanitizeTerminalText(result.screenshotPath)}`);
|
|
330
|
-
}
|
|
331
|
-
if (headed || keepOpen) {
|
|
332
|
-
printLine('Headed preview is running. Close Chromium or stop the command when you are done.');
|
|
333
|
-
}
|
|
334
|
-
if (result.warnings.length > 0) {
|
|
335
|
-
printLine(`Warnings: ${sanitizeTerminalText(result.warnings.join('; '))}`);
|
|
336
|
-
}
|
|
342
|
+
// A stop prints nothing more.
|
|
343
|
+
return 'interrupted' in outcome ? outcome.interrupted : undefined;
|
|
337
344
|
}
|
|
338
345
|
async function updateAppFromFile() {
|
|
339
346
|
const workspaceRoot = hostedWorkspaceRoot();
|
package/dist/auth-commands.js
CHANGED
|
@@ -4,7 +4,7 @@ import { sanitizeTerminalText, parseJsonResponse } from './json-response.js';
|
|
|
4
4
|
import { isInteractiveTerminal, hasArg, hasJsonFlag, getArg } from './command-line-arguments.js';
|
|
5
5
|
import { getBaseUrl, resolveLoginTarget, resolveLogoutTarget, assertSecureTokenTransport, authedFetch, } from './http.js';
|
|
6
6
|
import { clearUnreadableServerCredentials, hasResolvableAuth, rememberProcessSecret, saveServerCredentials, getServerCredentials, resolveAuthTokenForBaseUrl, } from './credentials.js';
|
|
7
|
-
import { printJson, printLine, printLoginProgress } from './cli-console-output.js';
|
|
7
|
+
import { printJson, printJsonWithHeldSecrets, printLine, printLoginProgress } from './cli-console-output.js';
|
|
8
8
|
import { resolveProjectSelection } from './account-directory.js';
|
|
9
9
|
function isNodeError(error) {
|
|
10
10
|
return error instanceof Error;
|
|
@@ -196,7 +196,7 @@ async function printEnv() {
|
|
|
196
196
|
const projectId = project?.id || getArg('--project-id') || process.env.ORIZU_PROJECT_ID || '';
|
|
197
197
|
const projectSlug = project ? `${project.teamSlug}/${project.slug}` : process.env.ORIZU_PROJECT || '';
|
|
198
198
|
if (hasJsonFlag()) {
|
|
199
|
-
|
|
199
|
+
printJsonWithHeldSecrets({
|
|
200
200
|
ORIZU_API_URL: baseUrl,
|
|
201
201
|
ORIZU_TOKEN: token,
|
|
202
202
|
ORIZU_PROJECT_ID: projectId,
|
|
@@ -8,5 +8,11 @@ declare function printError(message: string): void;
|
|
|
8
8
|
declare function printLoginProgress(message: string): void;
|
|
9
9
|
declare function printPushDisclosures(data: Record<string, unknown>): void;
|
|
10
10
|
declare function printJson(value: Record<string, unknown>): void;
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The one --json output allowed to carry a held secret: `orizu env --json`
|
|
13
|
+
* prints the token so a script can export it. Any new caller needs its own
|
|
14
|
+
* reviewed change and a case in cli-json-output-scrub-process.test.ts.
|
|
15
|
+
*/
|
|
16
|
+
declare function printJsonWithHeldSecrets(value: Record<string, unknown>): void;
|
|
17
|
+
export { getErrorMessage, printJson, printJsonWithHeldSecrets, printLine, printPushDisclosures, getCliVersion, printBannerIfInteractive, printVersion, printUsage, printError, printLoginProgress, };
|
|
12
18
|
//# sourceMappingURL=cli-console-output.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-console-output.d.ts","sourceRoot":"","sources":["../src/cli-console-output.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli-console-output.d.ts","sourceRoot":"","sources":["../src/cli-console-output.ts"],"names":[],"mappings":"AAWA,iBAAS,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE/C;AAED,iBAAS,aAAa,IAAI,MAAM,CAU/B;AAGD,iBAAS,SAAS,CAAC,OAAO,SAAK,QAE9B;AAED,iBAAS,YAAY,SAEpB;AAED,iBAAS,wBAAwB,SAMhC;AAED,iBAAS,UAAU,SAGlB;AAED,iBAAS,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEzC;AAED,iBAAS,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAMjD;AAED,iBAAS,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAMjE;AAED,iBAAS,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAEhD;AAED;;;;GAIG;AACH,iBAAS,wBAAwB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAI/D;AAED,OAAO,EACL,eAAe,EACf,SAAS,EACT,wBAAwB,EACxB,SAAS,EACT,oBAAoB,EACpB,aAAa,EACb,wBAAwB,EACxB,YAAY,EACZ,UAAU,EACV,UAAU,EACV,kBAAkB,GACnB,CAAA"}
|
|
@@ -4,7 +4,8 @@ import { stdout as output } from 'process';
|
|
|
4
4
|
import { renderBanner } from './banner.js';
|
|
5
5
|
import { renderRootHelp } from './help.js';
|
|
6
6
|
import { sanitizeTerminalText } from './json-response.js';
|
|
7
|
-
import { hasJsonFlag } from './command-line-arguments.js';
|
|
7
|
+
import { cliArgs, hasJsonFlag } from './command-line-arguments.js';
|
|
8
|
+
import { hideHeldSecrets } from './json-output-scrub.js';
|
|
8
9
|
import { reportReplacementWarning } from './report-replacement-warning.js';
|
|
9
10
|
function getErrorMessage(error) {
|
|
10
11
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -16,8 +17,9 @@ function getCliVersion() {
|
|
|
16
17
|
}
|
|
17
18
|
return packageJson.version;
|
|
18
19
|
}
|
|
20
|
+
// ORI-2744: under --json, stdout never carries a secret this process holds.
|
|
19
21
|
function printLine(message = '') {
|
|
20
|
-
output.write(`${message}\n`);
|
|
22
|
+
output.write(`${hasJsonFlag() ? hideHeldSecrets(message) : message}\n`);
|
|
21
23
|
}
|
|
22
24
|
function printVersion() {
|
|
23
25
|
printLine(`orizu ${getCliVersion()}`);
|
|
@@ -54,4 +56,15 @@ function printPushDisclosures(data) {
|
|
|
54
56
|
function printJson(value) {
|
|
55
57
|
printLine(JSON.stringify(value));
|
|
56
58
|
}
|
|
57
|
-
|
|
59
|
+
/**
|
|
60
|
+
* The one --json output allowed to carry a held secret: `orizu env --json`
|
|
61
|
+
* prints the token so a script can export it. Any new caller needs its own
|
|
62
|
+
* reviewed change and a case in cli-json-output-scrub-process.test.ts.
|
|
63
|
+
*/
|
|
64
|
+
function printJsonWithHeldSecrets(value) {
|
|
65
|
+
// Any other command is scrubbed as usual, so a wrong caller can't leak.
|
|
66
|
+
if (cliArgs[0] !== 'env')
|
|
67
|
+
return printJson(value);
|
|
68
|
+
output.write(`${JSON.stringify(value)}\n`);
|
|
69
|
+
}
|
|
70
|
+
export { getErrorMessage, printJson, printJsonWithHeldSecrets, printLine, printPushDisclosures, getCliVersion, printBannerIfInteractive, printVersion, printUsage, printError, printLoginProgress, };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
export interface Interrupted {
|
|
3
|
+
interrupted: 130 | 143;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Runs the program with Ctrl-C and SIGTERM turned into an interrupt, and says
|
|
7
|
+
* which stop arrived, if any. `once`: a second signal falls through to Node's
|
|
8
|
+
* default kill. The handlers are removed before this returns.
|
|
9
|
+
*/
|
|
10
|
+
export declare function runWithStops<A>(program: (signal: AbortSignal) => Effect.Effect<A, unknown>): Promise<{
|
|
11
|
+
exit: import("effect/Exit").Exit<A, unknown>;
|
|
12
|
+
interrupted: 130 | 143 | undefined;
|
|
13
|
+
}>;
|
|
14
|
+
//# sourceMappingURL=cli-stops.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-stops.d.ts","sourceRoot":"","sources":["../src/cli-stops.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAK/B,MAAM,WAAW,WAAW;IAAG,WAAW,EAAE,GAAG,GAAG,GAAG,CAAA;CAAE;AAEvD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC;;;GAchG"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
/**
|
|
3
|
+
* Runs the program with Ctrl-C and SIGTERM turned into an interrupt, and says
|
|
4
|
+
* which stop arrived, if any. `once`: a second signal falls through to Node's
|
|
5
|
+
* default kill. The handlers are removed before this returns.
|
|
6
|
+
*/
|
|
7
|
+
export async function runWithStops(program) {
|
|
8
|
+
const controller = new AbortController();
|
|
9
|
+
let interrupted;
|
|
10
|
+
const handleInterrupt = () => { interrupted ??= 130; controller.abort(); };
|
|
11
|
+
const handleTerminate = () => { interrupted ??= 143; controller.abort(); };
|
|
12
|
+
process.once('SIGINT', handleInterrupt);
|
|
13
|
+
process.once('SIGTERM', handleTerminate);
|
|
14
|
+
try {
|
|
15
|
+
const exit = await Effect.runPromiseExit(program(controller.signal), { signal: controller.signal });
|
|
16
|
+
return { exit, interrupted };
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
process.removeListener('SIGINT', handleInterrupt);
|
|
20
|
+
process.removeListener('SIGTERM', handleTerminate);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -9,15 +9,42 @@
|
|
|
9
9
|
* The BASE allowlist (Orizu API + model provider + git host) is code-owned and
|
|
10
10
|
* NOT shown/managed here — this surface only widens a team's egress with domains
|
|
11
11
|
* a customer workflow needs. Human ADMIN only: the server rejects agent bearers
|
|
12
|
-
* and non-admins (the definer RPC gates on is_team_admin).
|
|
13
|
-
*
|
|
12
|
+
* and non-admins (the definer RPC gates on is_team_admin). The arguments and
|
|
13
|
+
* output live here; the requests run on Effect in egress-allowlist-workflow.ts
|
|
14
|
+
* (ORI-2764), loaded only when this command runs.
|
|
14
15
|
*/
|
|
15
16
|
export type CliFetcher = (path: string, init?: RequestInit) => Promise<Response>;
|
|
16
17
|
export interface EgressAllowlistCommandIo {
|
|
17
18
|
json: boolean;
|
|
18
19
|
print: (line: string) => void;
|
|
19
20
|
printErr?: (line: string) => void;
|
|
21
|
+
/** Stands in for `authedFetch` in tests. */
|
|
20
22
|
fetcher?: CliFetcher;
|
|
21
23
|
}
|
|
24
|
+
export declare function splitDomains(value: string): string[];
|
|
25
|
+
export interface CurrentAllowlist {
|
|
26
|
+
domains: string[];
|
|
27
|
+
/** ALI-1174: legacy entries the server flags as failing the tightened
|
|
28
|
+
* validator — stored, shown by GET, but silently dropped by the
|
|
29
|
+
* coordinator at policy compose. Surfaced so "configured" never silently
|
|
30
|
+
* means "inert". */
|
|
31
|
+
invalid: string[];
|
|
32
|
+
/** Valid entries beyond the 100-domain cap — equally inert at compose. */
|
|
33
|
+
capped: string[];
|
|
34
|
+
}
|
|
35
|
+
export declare function stringArray(value: unknown): string[];
|
|
36
|
+
/**
|
|
37
|
+
* Re-derive the "beyond the 100-domain cap" entries from a stored set the
|
|
38
|
+
* SAME way the GET route does (positional over UNIQUE VALID domains): the
|
|
39
|
+
* coordinator applies only the first MAX_ADDITIVE_EGRESS_DOMAINS valid
|
|
40
|
+
* domains, so every valid entry past that index is configured-but-inert.
|
|
41
|
+
* Stored entries are already canonical + deduped by the write RPC, and the
|
|
42
|
+
* invalid (grandfathered) ones — flagged by the server — are not "valid"
|
|
43
|
+
* and so never consume a cap slot. Used for the PUT response, where a
|
|
44
|
+
* shrink-by-one on an oversized legacy row leaves the returned set still
|
|
45
|
+
* over-cap and the server does not send cappedDomains back.
|
|
46
|
+
*/
|
|
47
|
+
export declare function deriveCapped(stored: readonly string[], invalid: readonly string[]): string[];
|
|
48
|
+
export declare function emit(io: EgressAllowlistCommandIo, teamSlug: string, current: CurrentAllowlist): void;
|
|
22
49
|
export declare function egressAllowlistCommand(args: string[], io: EgressAllowlistCommandIo): Promise<number>;
|
|
23
50
|
//# sourceMappingURL=egress-allowlist-cli.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"egress-allowlist-cli.d.ts","sourceRoot":"","sources":["../src/egress-allowlist-cli.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"egress-allowlist-cli.d.ts","sourceRoot":"","sources":["../src/egress-allowlist-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AASH,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AAEhF,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,4CAA4C;IAC5C,OAAO,CAAC,EAAE,UAAU,CAAA;CACrB;AA0BD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAKpD;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB;;;yBAGqB;IACrB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAEpD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAU5F;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,wBAAwB,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI,CA6BpG;AAED,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAe1G"}
|
|
@@ -9,19 +9,16 @@
|
|
|
9
9
|
* The BASE allowlist (Orizu API + model provider + git host) is code-owned and
|
|
10
10
|
* NOT shown/managed here — this surface only widens a team's egress with domains
|
|
11
11
|
* a customer workflow needs. Human ADMIN only: the server rejects agent bearers
|
|
12
|
-
* and non-admins (the definer RPC gates on is_team_admin).
|
|
13
|
-
*
|
|
12
|
+
* and non-admins (the definer RPC gates on is_team_admin). The arguments and
|
|
13
|
+
* output live here; the requests run on Effect in egress-allowlist-workflow.ts
|
|
14
|
+
* (ORI-2764), loaded only when this command runs.
|
|
14
15
|
*/
|
|
15
|
-
import { authedFetch } from './http.js';
|
|
16
16
|
/** Mirrors MAX_TEAM_EGRESS_DOMAINS in the shared read-side validator
|
|
17
17
|
* (workers/session-coordinator/src/egress-domains.ts). The coordinator only
|
|
18
18
|
* applies the first this-many UNIQUE valid domains at policy compose; the
|
|
19
19
|
* CLI is a standalone package and cannot import the worker module, so the
|
|
20
20
|
* cap constant is duplicated here (kept in sync via that file's tests). */
|
|
21
21
|
const MAX_ADDITIVE_EGRESS_DOMAINS = 100;
|
|
22
|
-
function fetcherFrom(io) {
|
|
23
|
-
return io.fetcher ?? authedFetch;
|
|
24
|
-
}
|
|
25
22
|
function argValue(args, flag) {
|
|
26
23
|
const index = args.indexOf(flag);
|
|
27
24
|
if (index === -1 || index + 1 >= args.length || args[index + 1].startsWith('--')) {
|
|
@@ -45,23 +42,13 @@ function printErr(io, message) {
|
|
|
45
42
|
;
|
|
46
43
|
(io.printErr ?? io.print)(message);
|
|
47
44
|
}
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const data = (await response.clone().json());
|
|
51
|
-
const error = typeof data.error === 'string' ? data.error : null;
|
|
52
|
-
return error || response.statusText || String(response.status);
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
return response.statusText || String(response.status);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function splitDomains(value) {
|
|
45
|
+
export function splitDomains(value) {
|
|
59
46
|
return value
|
|
60
47
|
.split(',')
|
|
61
48
|
.map(d => d.trim())
|
|
62
49
|
.filter(d => d.length > 0);
|
|
63
50
|
}
|
|
64
|
-
function stringArray(value) {
|
|
51
|
+
export function stringArray(value) {
|
|
65
52
|
return Array.isArray(value) ? value.filter((d) => typeof d === 'string') : [];
|
|
66
53
|
}
|
|
67
54
|
/**
|
|
@@ -75,7 +62,7 @@ function stringArray(value) {
|
|
|
75
62
|
* shrink-by-one on an oversized legacy row leaves the returned set still
|
|
76
63
|
* over-cap and the server does not send cappedDomains back.
|
|
77
64
|
*/
|
|
78
|
-
function deriveCapped(stored, invalid) {
|
|
65
|
+
export function deriveCapped(stored, invalid) {
|
|
79
66
|
const invalidSet = new Set(invalid);
|
|
80
67
|
const capped = [];
|
|
81
68
|
let validCount = 0;
|
|
@@ -88,19 +75,7 @@ function deriveCapped(stored, invalid) {
|
|
|
88
75
|
}
|
|
89
76
|
return capped;
|
|
90
77
|
}
|
|
91
|
-
|
|
92
|
-
const response = await fetcher(path, { method: 'GET' });
|
|
93
|
-
if (!response.ok) {
|
|
94
|
-
throw new Error(`show failed (${response.status}): ${await responseMessage(response)}`);
|
|
95
|
-
}
|
|
96
|
-
const data = (await response.json());
|
|
97
|
-
return {
|
|
98
|
-
domains: stringArray(data.extraDomains),
|
|
99
|
-
invalid: stringArray(data.invalidDomains),
|
|
100
|
-
capped: stringArray(data.cappedDomains),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
function emit(io, teamSlug, current) {
|
|
78
|
+
export function emit(io, teamSlug, current) {
|
|
104
79
|
if (io.json) {
|
|
105
80
|
io.print(JSON.stringify({
|
|
106
81
|
teamSlug,
|
|
@@ -134,61 +109,13 @@ export async function egressAllowlistCommand(args, io) {
|
|
|
134
109
|
printErr(io, 'Usage: orizu team egress-allowlist <team> [--add <domain>|--remove <domain>|--set a.com,b.com|--clear] [--json]');
|
|
135
110
|
return 1;
|
|
136
111
|
}
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
emit(io, teamSlug, await fetchCurrent(fetcher, path));
|
|
147
|
-
return 0;
|
|
148
|
-
}
|
|
149
|
-
// Compute the next set. --set/--clear replace; --add/--remove mutate current.
|
|
150
|
-
let next;
|
|
151
|
-
if (setValue !== null) {
|
|
152
|
-
next = splitDomains(setValue);
|
|
153
|
-
}
|
|
154
|
-
else if (clear) {
|
|
155
|
-
next = [];
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
const current = await fetchCurrent(fetcher, path);
|
|
159
|
-
const set = new Set(current.domains);
|
|
160
|
-
if (addValue)
|
|
161
|
-
for (const d of splitDomains(addValue))
|
|
162
|
-
set.add(d);
|
|
163
|
-
if (removeValue)
|
|
164
|
-
for (const d of splitDomains(removeValue))
|
|
165
|
-
set.delete(d);
|
|
166
|
-
next = [...set];
|
|
167
|
-
}
|
|
168
|
-
const response = await fetcher(path, {
|
|
169
|
-
method: 'PUT',
|
|
170
|
-
headers: { 'Content-Type': 'application/json' },
|
|
171
|
-
body: JSON.stringify({ domains: next }),
|
|
172
|
-
});
|
|
173
|
-
if (!response.ok) {
|
|
174
|
-
printErr(io, `egress-allowlist update failed (${response.status}): ${await responseMessage(response)}`);
|
|
175
|
-
return 1;
|
|
176
|
-
}
|
|
177
|
-
const data = (await response.json());
|
|
178
|
-
const stored = Array.isArray(data.extraDomains) ? stringArray(data.extraDomains) : next;
|
|
179
|
-
const invalid = stringArray(data.invalidDomains);
|
|
180
|
-
// Grandfathered retention: a successful PUT may still STORE legacy
|
|
181
|
-
// invalid entries the admin retained — the server flags them and we show
|
|
182
|
-
// it. Round 5 (P2): a >100-entry legacy row can only shrink one entry per
|
|
183
|
-
// PUT (the write RPC's GREATEST(100, stored-1) bound), so the returned
|
|
184
|
-
// set can still be OVER the cap. Re-derive the cap annotations from the
|
|
185
|
-
// returned set — same positional logic GET uses — instead of assuming
|
|
186
|
-
// the RPC left it at/under 100.
|
|
187
|
-
emit(io, teamSlug, { domains: stored, invalid, capped: deriveCapped(stored, invalid) });
|
|
188
|
-
return 0;
|
|
189
|
-
}
|
|
190
|
-
catch (error) {
|
|
191
|
-
printErr(io, error instanceof Error ? error.message : String(error));
|
|
192
|
-
return 1;
|
|
193
|
-
}
|
|
112
|
+
const { runEgressAllowlist } = await import('./egress-allowlist-workflow.js');
|
|
113
|
+
return await runEgressAllowlist({
|
|
114
|
+
teamSlug,
|
|
115
|
+
path: `/api/cli/teams/${encodeURIComponent(teamSlug)}/egress-allowlist`,
|
|
116
|
+
setValue: argValue(args, '--set'),
|
|
117
|
+
addValue: argValue(args, '--add'),
|
|
118
|
+
removeValue: argValue(args, '--remove'),
|
|
119
|
+
clear: args.includes('--clear'),
|
|
120
|
+
}, io);
|
|
194
121
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type EgressAllowlistCommandIo } from './egress-allowlist-cli.js';
|
|
2
|
+
/** What one run does: show the list, or change it. */
|
|
3
|
+
export interface EgressAllowlistRun {
|
|
4
|
+
teamSlug: string;
|
|
5
|
+
path: string;
|
|
6
|
+
setValue: string | null;
|
|
7
|
+
addValue: string | null;
|
|
8
|
+
removeValue: string | null;
|
|
9
|
+
clear: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Runs one `orizu team egress-allowlist` command to its exit code. Ctrl-C or
|
|
13
|
+
* SIGTERM ends the wait with 130 or 143; once the PUT was sent the person is
|
|
14
|
+
* told the update may be saved.
|
|
15
|
+
*/
|
|
16
|
+
export declare function runEgressAllowlist(run: EgressAllowlistRun, io: EgressAllowlistCommandIo): Promise<number>;
|
|
17
|
+
//# sourceMappingURL=egress-allowlist-workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"egress-allowlist-workflow.d.ts","sourceRoot":"","sources":["../src/egress-allowlist-workflow.ts"],"names":[],"mappings":"AAaA,OAAO,EAML,KAAK,wBAAwB,EAE9B,MAAM,2BAA2B,CAAA;AAoFlC,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,KAAK,EAAE,OAAO,CAAA;CACf;AAkDD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,kBAAkB,EAAE,EAAE,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6B/G"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// ORI-2764 (ORI-2735c): `orizu team egress-allowlist` on Effect. Loaded only
|
|
2
|
+
// through `await import(...)` from egress-allowlist-cli.ts, so other commands
|
|
3
|
+
// never load Effect.
|
|
4
|
+
//
|
|
5
|
+
// - Each request has the dataset-transfer deadline (120 s by default).
|
|
6
|
+
// - The PUT is the only write. Once it was sent, a lost or late reply may
|
|
7
|
+
// still be saved. Every change is safe to repeat (`--set`/`--clear` replace
|
|
8
|
+
// the list, `--add`/`--remove` read it again first), so the CLI says
|
|
9
|
+
// running the same command again is safe.
|
|
10
|
+
// - Ctrl-C and SIGTERM stop a wait with 130 and 143.
|
|
11
|
+
import { Cause, Effect, Exit } from 'effect';
|
|
12
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js';
|
|
13
|
+
import { deriveCapped, emit, splitDomains, stringArray, } from './egress-allowlist-cli.js';
|
|
14
|
+
import { authedFetch } from './http.js';
|
|
15
|
+
import { mayAlreadyBeSavedLine } from './one-request-write.js';
|
|
16
|
+
import { unreachedCode } from './request-outcome.js';
|
|
17
|
+
const MAY_BE = mayAlreadyBeSavedLine('egress allowlist', 'updated', 'running the same command again is safe');
|
|
18
|
+
/** A failure the command prints as one message, plus the may-be line when the update may be saved. */
|
|
19
|
+
class EgressFailure {
|
|
20
|
+
message;
|
|
21
|
+
mayBeSaved;
|
|
22
|
+
constructor(message, mayBeSaved = false) {
|
|
23
|
+
this.message = message;
|
|
24
|
+
this.mayBeSaved = mayBeSaved;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** The server's `{ error }`, else the status text, else the status, as before. */
|
|
28
|
+
function replyMessage(reply) {
|
|
29
|
+
try {
|
|
30
|
+
const data = JSON.parse(reply.text);
|
|
31
|
+
const error = typeof data.error === 'string' ? data.error : null;
|
|
32
|
+
return error || reply.statusText || String(reply.status);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return reply.statusText || String(reply.status);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* One request. A deadline says so under `label`; any other failure keeps
|
|
40
|
+
* today's text. For the PUT, a failure after sending may be saved.
|
|
41
|
+
*/
|
|
42
|
+
function request(fetcher, path, init, label, timeoutMs, progress) {
|
|
43
|
+
return Effect.suspend(() => {
|
|
44
|
+
let sent = false;
|
|
45
|
+
let timedOut = false;
|
|
46
|
+
return Effect.tryPromise({
|
|
47
|
+
try: async (signal) => {
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
const abort = () => controller.abort();
|
|
50
|
+
const timer = setTimeout(() => { timedOut = true; controller.abort(); }, timeoutMs);
|
|
51
|
+
if (signal.aborted)
|
|
52
|
+
abort();
|
|
53
|
+
else
|
|
54
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
55
|
+
const markSent = () => { sent = true; if (progress)
|
|
56
|
+
progress.putSent = true; };
|
|
57
|
+
try {
|
|
58
|
+
const requestInit = { ...init, signal: controller.signal };
|
|
59
|
+
const response = fetcher
|
|
60
|
+
? (markSent(), await fetcher(path, requestInit))
|
|
61
|
+
: await authedFetch(path, requestInit, markSent);
|
|
62
|
+
return { status: response.status, ok: response.ok, statusText: response.statusText, text: await response.text() };
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
signal.removeEventListener('abort', abort);
|
|
67
|
+
controller.abort();
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
catch: cause => {
|
|
71
|
+
const mayBeSaved = progress !== null && sent;
|
|
72
|
+
if (timedOut)
|
|
73
|
+
return new EgressFailure(`${label}: the request exceeded its deadline`, mayBeSaved);
|
|
74
|
+
// Refused before it reached the server, or a 401 whose sign-in could not be renewed: nothing saved.
|
|
75
|
+
const refused = unreachedCode(cause) !== null || cause?.requestRejected === true;
|
|
76
|
+
return new EgressFailure(cause instanceof Error ? cause.message : String(cause), mayBeSaved && !refused);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/** A 2xx reply's JSON object; a non-JSON reply fails with the parse error, as before. */
|
|
82
|
+
function parseReply(reply) {
|
|
83
|
+
return Effect.try({
|
|
84
|
+
try: () => JSON.parse(reply.text),
|
|
85
|
+
catch: cause => new EgressFailure(cause instanceof Error ? cause.message : String(cause)),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function readCurrent(fetcher, path, timeoutMs) {
|
|
89
|
+
return Effect.gen(function* () {
|
|
90
|
+
const reply = yield* request(fetcher, path, { method: 'GET' }, 'show failed', timeoutMs, null);
|
|
91
|
+
if (!reply.ok)
|
|
92
|
+
return yield* Effect.fail(new EgressFailure(`show failed (${reply.status}): ${replyMessage(reply)}`));
|
|
93
|
+
const data = yield* parseReply(reply);
|
|
94
|
+
return { domains: stringArray(data.extraDomains), invalid: stringArray(data.invalidDomains), capped: stringArray(data.cappedDomains) };
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const isMutation = (run) => run.setValue !== null || run.addValue !== null || run.removeValue !== null || run.clear;
|
|
98
|
+
function egressAllowlist(run, io, timeoutMs, progress) {
|
|
99
|
+
return Effect.gen(function* () {
|
|
100
|
+
if (!isMutation(run)) {
|
|
101
|
+
emit(io, run.teamSlug, yield* readCurrent(io.fetcher, run.path, timeoutMs));
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
// --set/--clear replace; --add/--remove change the list read first.
|
|
105
|
+
let next;
|
|
106
|
+
if (run.setValue !== null) {
|
|
107
|
+
next = splitDomains(run.setValue);
|
|
108
|
+
}
|
|
109
|
+
else if (run.clear) {
|
|
110
|
+
next = [];
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const current = yield* readCurrent(io.fetcher, run.path, timeoutMs);
|
|
114
|
+
const set = new Set(current.domains);
|
|
115
|
+
if (run.addValue)
|
|
116
|
+
for (const d of splitDomains(run.addValue))
|
|
117
|
+
set.add(d);
|
|
118
|
+
if (run.removeValue)
|
|
119
|
+
for (const d of splitDomains(run.removeValue))
|
|
120
|
+
set.delete(d);
|
|
121
|
+
next = [...set];
|
|
122
|
+
}
|
|
123
|
+
const label = 'egress-allowlist update failed';
|
|
124
|
+
const reply = yield* request(io.fetcher, run.path, {
|
|
125
|
+
method: 'PUT',
|
|
126
|
+
headers: { 'Content-Type': 'application/json' },
|
|
127
|
+
body: JSON.stringify({ domains: next }),
|
|
128
|
+
}, label, timeoutMs, progress);
|
|
129
|
+
if (!reply.ok) {
|
|
130
|
+
// A refusal (4xx) saved nothing; a 5xx after the PUT may have.
|
|
131
|
+
return yield* Effect.fail(new EgressFailure(`${label} (${reply.status}): ${replyMessage(reply)}`, reply.status >= 500));
|
|
132
|
+
}
|
|
133
|
+
const data = yield* parseReply(reply);
|
|
134
|
+
const stored = Array.isArray(data.extraDomains) ? stringArray(data.extraDomains) : next;
|
|
135
|
+
const invalid = stringArray(data.invalidDomains);
|
|
136
|
+
// Grandfathered retention: a successful PUT may still STORE legacy
|
|
137
|
+
// invalid entries the admin retained — the server flags them and we show
|
|
138
|
+
// it. Round 5 (P2): a >100-entry legacy row can only shrink one entry per
|
|
139
|
+
// PUT (the write RPC's GREATEST(100, stored-1) bound), so the returned
|
|
140
|
+
// set can still be OVER the cap. Re-derive the cap annotations from the
|
|
141
|
+
// returned set — same positional logic GET uses — instead of assuming
|
|
142
|
+
// the RPC left it at/under 100.
|
|
143
|
+
emit(io, run.teamSlug, { domains: stored, invalid, capped: deriveCapped(stored, invalid) });
|
|
144
|
+
return 0;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Runs one `orizu team egress-allowlist` command to its exit code. Ctrl-C or
|
|
149
|
+
* SIGTERM ends the wait with 130 or 143; once the PUT was sent the person is
|
|
150
|
+
* told the update may be saved.
|
|
151
|
+
*/
|
|
152
|
+
export async function runEgressAllowlist(run, io) {
|
|
153
|
+
const printErr = io.printErr ?? io.print;
|
|
154
|
+
const controller = new AbortController();
|
|
155
|
+
const progress = { putSent: false };
|
|
156
|
+
let interrupted;
|
|
157
|
+
const interrupt = () => { interrupted ??= 130; controller.abort(); };
|
|
158
|
+
const terminate = () => { interrupted ??= 143; controller.abort(); };
|
|
159
|
+
process.on('SIGINT', interrupt);
|
|
160
|
+
process.on('SIGTERM', terminate);
|
|
161
|
+
try {
|
|
162
|
+
const exit = await Effect.runPromiseExit(egressAllowlist(run, io, datasetTransferTimeoutMs(), progress), { signal: controller.signal });
|
|
163
|
+
if (Exit.isSuccess(exit))
|
|
164
|
+
return exit.value;
|
|
165
|
+
if (interrupted !== undefined) {
|
|
166
|
+
const message = isMutation(run) ? 'Egress allowlist update interrupted.' : 'Egress allowlist read interrupted.';
|
|
167
|
+
printErr(progress.putSent ? `${message}\n${MAY_BE}` : message);
|
|
168
|
+
return interrupted;
|
|
169
|
+
}
|
|
170
|
+
// Anything else that went wrong prints its message with exit 1, as before.
|
|
171
|
+
const failure = Cause.squash(exit.cause);
|
|
172
|
+
if (!(failure instanceof EgressFailure)) {
|
|
173
|
+
printErr(failure instanceof Error ? failure.message : String(failure));
|
|
174
|
+
return 1;
|
|
175
|
+
}
|
|
176
|
+
printErr(failure.mayBeSaved ? `${failure.message}\n${MAY_BE}` : failure.message);
|
|
177
|
+
return 1;
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
process.removeListener('SIGINT', interrupt);
|
|
181
|
+
process.removeListener('SIGTERM', terminate);
|
|
182
|
+
}
|
|
183
|
+
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAuLA,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAEhE,eAAO,MAAM,wBAAwB,GAAI,iBAAiB,MAAM;;;EACJ,CAAA;AAE5D,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAmqCtD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CA0B1G;AAED,eAAO,MAAM,iBAAiB,SAAU,CAAA;AACxC,eAAO,MAAM,uBAAuB,QAAkB,CAAA;AACtD,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,UAAU,CAexH;AAmMD,wBAAsB,IAAI,CAAC,OAAO,WAAwB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAuLA,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAEhE,eAAO,MAAM,wBAAwB,GAAI,iBAAiB,MAAM;;;EACJ,CAAA;AAE5D,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAmqCtD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CA0B1G;AAED,eAAO,MAAM,iBAAiB,SAAU,CAAA;AACxC,eAAO,MAAM,uBAAuB,QAAkB,CAAA;AACtD,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,UAAU,CAexH;AAmMD,wBAAsB,IAAI,CAAC,OAAO,WAAwB,iBAuiBzD;AAuED,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAEhF,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1565,7 +1565,13 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
1565
1565
|
return;
|
|
1566
1566
|
}
|
|
1567
1567
|
if (command === 'apps' && subcommand === 'preview') {
|
|
1568
|
-
await previewAppFromFile();
|
|
1568
|
+
const stopped = await previewAppFromFile();
|
|
1569
|
+
if (stopped !== undefined) {
|
|
1570
|
+
process.exitCode = stopped;
|
|
1571
|
+
// A Chromium launch can't be cancelled; one still starting holds the process
|
|
1572
|
+
// until it arrives and is closed. Exit anyway once the 10 s budget is spent.
|
|
1573
|
+
setTimeout(() => process.exit(stopped), 10_000).unref();
|
|
1574
|
+
}
|
|
1569
1575
|
return;
|
|
1570
1576
|
}
|
|
1571
1577
|
if (command === 'apps' && subcommand === 'update') {
|