@roarkanalytics/cli 0.1.0 → 0.2.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.
@@ -1,28 +1,33 @@
1
- .\" File generated from our OpenAPI spec by app-agent-codegen. Do not edit.
1
+ .\" Hand-maintained. `config apply` is a hand-written command
2
+ .\" (packages/cli/src/runtime/commands/config-as-code.ts): `program.ts` filters the
3
+ .\" generated one out, so this page documents the runtime, not the endpoint.
4
+ .\" sdk.yml lists it under targets.cli.hand_maintained_commands, which is what stops
5
+ .\" app-agent-codegen regenerating this file. Removing that entry silently clobbers it.
2
6
  .TH "ROARK\-CONFIG\-APPLY" "1" "" "roark-config-apply" "Roark Analytics API Manual"
3
7
  .SH "NAME"
4
- roark\-config\-apply \- Apply a config bundle
8
+ roark\-config\-apply \- Reconcile the project to a config-as-code directory
5
9
  .SH "SYNOPSIS"
6
10
  .B roark config apply
7
11
  [\fIoptions\fR]
12
+ [\fIdir\fR]
8
13
  .SH "DESCRIPTION"
9
- Reconcile a config\-as\-code bundle into the project. Submit the full desired set of resources; resources already managed by config are updated, new ones created, and (unless prune is false) config\-managed resources absent from the bundle are deleted. Identity is by name no ids in the bundle.
10
- .SH "API"
11
- POST /v1/config/apply
12
- .SH "OPTIONS"
13
- .TP
14
- \fB\-\-resources\fR \fI<value>\fR
15
- Required.
14
+ Build a config-as-code bundle from a directory of YAML files and reconcile the project to it: resources already managed by config are updated, new ones created, and (unless \fB\-\-no\-prune\fR) config-managed resources absent from the bundle are deleted. Identity is by name (no ids in the bundle).
15
+ .PP
16
+ It first previews the diff and asks to confirm before writing, so a prune-driven delete is never a surprise. Pass \fB\-\-yes\fR to skip the prompt (e.g. in CI). Point it at a config directory (reads \fB*.yaml\fR / \fB*.yml\fR, inlines \fBfile://\fR references), or supply a pre-built bundle with \fB\-\-data\fR / stdin.
17
+ .SH "ARGUMENTS"
16
18
  .TP
17
- \fB\-\-prune\fR
18
- Optional.
19
- .SH "GLOBAL OPTIONS"
19
+ \fIdir\fR
20
+ A directory of config resources (\fB*.yaml\fR). Omit it to supply a pre-built bundle with \fB\-\-data\fR or on stdin instead.
21
+ .SH "OPTIONS"
20
22
  .TP
21
23
  \fB\-\-data\fR \fI<json>\fR
22
- Request body as JSON, or @file to read one (@\- for stdin).
24
+ Pre-built bundle as JSON, or @file (@\- for stdin); an alternative to a directory.
25
+ .TP
26
+ \fB\-\-no\-prune\fR
27
+ Do not delete config-managed resources that are absent from the bundle.
23
28
  .TP
24
29
  \fB\-y\fR, \fB\-\-yes\fR
25
- Skip confirmation prompts.
30
+ Skip the confirmation prompt.
26
31
  .TP
27
32
  \fB\-\-no\-input\fR
28
33
  Never prompt; fail instead.
@@ -47,17 +52,12 @@ Shorthand for \-\-format json.
47
52
  .TP
48
53
  \fB\-\-no\-color\fR
49
54
  Disable colour.
50
- .TP
51
- \fB\-q\fR, \fB\-\-quiet\fR
52
- Suppress non\-essential output.
53
- .TP
54
- \fB\-\-allow\-project\-base\-url\fR
55
- Send the stored credential to a base URL set by a project .roark.json. Refused by default.
56
55
  .SH "EXAMPLES"
57
56
  .PP
58
57
  .RS 4
59
58
  .nf
60
- roark config apply \-\-resources <value>
59
+ roark config apply ./roark
60
+ roark config apply ./roark \-\-yes \-\-no\-prune
61
61
  .fi
62
62
  .RE
63
63
  .SH "ENVIRONMENT"
@@ -68,9 +68,6 @@ Bearer token used when \-\-token is not given.
68
68
  \fBROARK_BASE_URL\fR
69
69
  API base URL.
70
70
  .TP
71
- \fBROARK_ALLOW_PROJECT_BASE_URL\fR
72
- Trusts a base URL set by a project .roark.json, like \-\-allow\-project\-base\-url.
73
- .TP
74
71
  \fBNO_COLOR\fR
75
72
  Disables colour when set to any value.
76
73
  .SH "EXIT STATUS"
@@ -83,14 +80,6 @@ The API rejected the request.
83
80
  .TP
84
81
  \fB2\fR
85
82
  The command line was wrong.
86
- .TP
87
- \fB3\fR
88
- No credential, or the credential was refused.
89
- .TP
90
- \fB4\fR
91
- The addressed resource does not exist.
92
- .TP
93
- \fB5\fR
94
- The request never completed: connection, timeout, or abort.
95
83
  .SH "SEE ALSO"
96
- .BR roark (1)
84
+ .BR roark (1),
85
+ .BR roark-config-diff (1)
@@ -1,31 +1,30 @@
1
- .\" File generated from our OpenAPI spec by app-agent-codegen. Do not edit.
1
+ .\" Hand-maintained. `config diff` is a hand-written command
2
+ .\" (packages/cli/src/runtime/commands/config-as-code.ts): `program.ts` filters the
3
+ .\" generated one out, so this page documents the runtime, not the endpoint.
4
+ .\" sdk.yml lists it under targets.cli.hand_maintained_commands, which is what stops
5
+ .\" app-agent-codegen regenerating this file. Removing that entry silently clobbers it.
2
6
  .TH "ROARK\-CONFIG\-DIFF" "1" "" "roark-config-diff" "Roark Analytics API Manual"
3
7
  .SH "NAME"
4
- roark\-config\-diff \- Diff a config bundle
8
+ roark\-config\-diff \- Preview config-as-code changes
5
9
  .SH "SYNOPSIS"
6
10
  .B roark config diff
7
11
  [\fIoptions\fR]
12
+ [\fIdir\fR]
8
13
  .SH "DESCRIPTION"
9
- Dry run: returns the changes reconcile a config\-as\-code bundle into the project. Submit the full desired set of resources; resources already managed by config are updated, new ones created, and (unless prune is false) config\-managed resources absent from the bundle are deleted. Identity is by name — no ids in the bundle. No writes are performed.
10
- .SH "API"
11
- POST /v1/config/diff
12
- .SH "OPTIONS"
13
- .TP
14
- \fB\-\-resources\fR \fI<value>\fR
15
- Required.
14
+ Build a config-as-code bundle from a directory of YAML files and show the changes it would make against the project. No writes are performed.
15
+ .PP
16
+ Point it at a config directory and it reads every \fB*.yaml\fR / \fB*.yml\fR under it, inlines any \fBfile://\fR references, and submits the resulting bundle to \fBPOST /v1/config/diff\fR. The server computes the diff; identity is by name (no ids in the bundle). Run this before \fBconfig apply\fR to preview.
17
+ .SH "ARGUMENTS"
16
18
  .TP
17
- \fB\-\-prune\fR
18
- Optional.
19
- .SH "GLOBAL OPTIONS"
19
+ \fIdir\fR
20
+ A directory of config resources (\fB*.yaml\fR). Omit it to supply a pre-built bundle with \fB\-\-data\fR or on stdin instead.
21
+ .SH "OPTIONS"
20
22
  .TP
21
23
  \fB\-\-data\fR \fI<json>\fR
22
- Request body as JSON, or @file to read one (@\- for stdin).
23
- .TP
24
- \fB\-y\fR, \fB\-\-yes\fR
25
- Skip confirmation prompts.
24
+ Pre-built bundle as JSON, or @file (@\- for stdin); an alternative to a directory.
26
25
  .TP
27
- \fB\-\-no\-input\fR
28
- Never prompt; fail instead.
26
+ \fB\-\-no\-prune\fR
27
+ Do not delete config-managed resources that are absent from the bundle.
29
28
  .TP
30
29
  \fB\-\-base\-url\fR \fI<url>\fR
31
30
  API base URL.
@@ -47,17 +46,12 @@ Shorthand for \-\-format json.
47
46
  .TP
48
47
  \fB\-\-no\-color\fR
49
48
  Disable colour.
50
- .TP
51
- \fB\-q\fR, \fB\-\-quiet\fR
52
- Suppress non\-essential output.
53
- .TP
54
- \fB\-\-allow\-project\-base\-url\fR
55
- Send the stored credential to a base URL set by a project .roark.json. Refused by default.
56
49
  .SH "EXAMPLES"
57
50
  .PP
58
51
  .RS 4
59
52
  .nf
60
- roark config diff \-\-resources <value>
53
+ roark config diff ./roark
54
+ cat bundle.json | roark config diff \-\-data @\-
61
55
  .fi
62
56
  .RE
63
57
  .SH "ENVIRONMENT"
@@ -68,9 +62,6 @@ Bearer token used when \-\-token is not given.
68
62
  \fBROARK_BASE_URL\fR
69
63
  API base URL.
70
64
  .TP
71
- \fBROARK_ALLOW_PROJECT_BASE_URL\fR
72
- Trusts a base URL set by a project .roark.json, like \-\-allow\-project\-base\-url.
73
- .TP
74
65
  \fBNO_COLOR\fR
75
66
  Disables colour when set to any value.
76
67
  .SH "EXIT STATUS"
@@ -83,14 +74,6 @@ The API rejected the request.
83
74
  .TP
84
75
  \fB2\fR
85
76
  The command line was wrong.
86
- .TP
87
- \fB3\fR
88
- No credential, or the credential was refused.
89
- .TP
90
- \fB4\fR
91
- The addressed resource does not exist.
92
- .TP
93
- \fB5\fR
94
- The request never completed: connection, timeout, or abort.
95
77
  .SH "SEE ALSO"
96
- .BR roark (1)
78
+ .BR roark (1),
79
+ .BR roark-config-apply (1)
@@ -13,13 +13,13 @@ POST /v1/metric/definitions
13
13
  This endpoint accepts one of several request shapes, so the body is supplied as JSON through \e\-\e\-data or on standard input rather than as individual flags.
14
14
  .TP
15
15
  \fBPromptMetricInput\fR
16
- Requires calculationType, name, outputType, analysisPackageId.
16
+ Requires calculationType, name, outputType.
17
17
  .TP
18
18
  \fBFormulaMetricInput\fR
19
- Requires calculationType, name, outputType, formula, sources, analysisPackageId.
19
+ Requires calculationType, name, outputType, formula, sources.
20
20
  .TP
21
21
  \fBPatternMetricInput\fR
22
- Requires calculationType, name, operation, outcome, analysisPackageId.
22
+ Requires calculationType, name, operation, outcome.
23
23
  .SH "GLOBAL OPTIONS"
24
24
  .TP
25
25
  \fB\-\-data\fR \fI<json>\fR
@@ -29,6 +29,9 @@ Languages the persona can understand. Defaults to the languages the persona spea
29
29
  \fB\-\-accent\fR \fI<value>\fR
30
30
  Accent of the persona, defined using ISO 3166\-1 alpha\-2 country codes with optional variants One of: US, US_X_SOUTH, GB, ES, DE, IN, FR, NL, SA, GR, AU, IT, ID, TH, JP, NZ, PH, SG, MY, HK, TR, PT, IL. Required.
31
31
  .TP
32
+ \fB\-\-age\fR \fI<value>\fR
33
+ How old the caller sounds and behaves. Only ages the persona's accent has a voice for are accepted; defaults to ADULT, which every accent supports. One of: CHILD, TEENAGER, ADULT, ELDERLY. Optional.
34
+ .TP
32
35
  \fB\-\-gender\fR \fI<value>\fR
33
36
  Gender of the persona One of: MALE, FEMALE. Required.
34
37
  .TP
@@ -33,6 +33,9 @@ Languages the persona can understand. Multilingual combinations are limited by m
33
33
  \fB\-\-accent\fR \fI<value>\fR
34
34
  Accent of the persona, defined using ISO 3166\-1 alpha\-2 country codes with optional variants One of: US, US_X_SOUTH, GB, ES, DE, IN, FR, NL, SA, GR, AU, IT, ID, TH, JP, NZ, PH, SG, MY, HK, TR, PT, IL. Optional.
35
35
  .TP
36
+ \fB\-\-age\fR \fI<value>\fR
37
+ How old the caller sounds and behaves. Only ages the persona's accent has a voice for are accepted; defaults to ADULT, which every accent supports. One of: CHILD, TEENAGER, ADULT, ELDERLY. Optional.
38
+ .TP
36
39
  \fB\-\-gender\fR \fI<value>\fR
37
40
  Gender of the persona One of: MALE, FEMALE. Optional.
38
41
  .TP
package/man/roark.1 CHANGED
@@ -103,10 +103,10 @@ List call sentiment runs See roark\-call\-sentiment\-run\-list(1).
103
103
  Get call transcript See roark\-call\-transcript\-get(1).
104
104
  .TP
105
105
  \fBroark config apply\fR
106
- Apply a config bundle See roark\-config\-apply(1).
106
+ Reconcile the project to a config directory (or bundle). Previews and confirms first. See roark\-config\-apply(1).
107
107
  .TP
108
108
  \fBroark config diff\fR
109
- Diff a config bundle See roark\-config\-diff(1).
109
+ Preview the changes a config directory (or bundle) would make. No writes. See roark\-config\-diff(1).
110
110
  .TP
111
111
  \fBroark customer\-flow create\fR
112
112
  Create a customer flow See roark\-customer\-flow\-create(1).
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@roarkanalytics/cli",
3
+ "version": "0.2.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "@roarkanalytics/cli",
9
+ "version": "0.2.0",
10
+ "license": "Apache-2.0",
11
+ "dependencies": {
12
+ "@roarkanalytics/sdk": "^3.2.0",
13
+ "commander": "^14.0.3",
14
+ "yaml": "^2.9.0"
15
+ },
16
+ "bin": {
17
+ "roark": "bin.js"
18
+ },
19
+ "engines": {
20
+ "node": ">=20"
21
+ }
22
+ },
23
+ "node_modules/@roarkanalytics/sdk": {
24
+ "version": "3.2.0",
25
+ "resolved": "https://registry.npmjs.org/@roarkanalytics/sdk/-/sdk-3.2.0.tgz",
26
+ "integrity": "sha512-dZm7nhil+zSvh1jGWOp12znFbn1HYx5SocvzrDS82ljPQUATVqyLkloc0ANft3pSb3SiaaNuWd/Z46z912lTxg==",
27
+ "license": "Apache-2.0"
28
+ },
29
+ "node_modules/commander": {
30
+ "version": "14.0.3",
31
+ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
32
+ "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
33
+ "license": "MIT",
34
+ "engines": {
35
+ "node": ">=20"
36
+ }
37
+ },
38
+ "node_modules/yaml": {
39
+ "version": "2.9.0",
40
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
41
+ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
42
+ "license": "ISC",
43
+ "bin": {
44
+ "yaml": "bin.mjs"
45
+ },
46
+ "engines": {
47
+ "node": ">= 14.6"
48
+ },
49
+ "funding": {
50
+ "url": "https://github.com/sponsors/eemeli"
51
+ }
52
+ }
53
+ }
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roarkanalytics/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "The official command line interface for the Roark API",
5
5
  "author": "Roark <james@roark.ai>",
6
6
  "type": "commonjs",
@@ -30,8 +30,9 @@
30
30
  "fix": "eslint --fix ."
31
31
  },
32
32
  "dependencies": {
33
- "@roarkanalytics/sdk": "^2.33.0",
34
- "commander": "^14.0.3"
33
+ "@roarkanalytics/sdk": "^3.2.0",
34
+ "commander": "^14.0.3",
35
+ "yaml": "^2.9.0"
35
36
  },
36
37
  "man": [
37
38
  "./man/roark.1",
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `roark config diff [dir]` and `roark config apply [dir]` - config as code.
3
+ *
4
+ * These wrap the generated `config.diff` / `config.apply` SDK methods with the one client-side
5
+ * step they need to be useful: building the bundle from a directory of YAML files. Point either
6
+ * at a config directory and it reads `*.yaml`, inlines `file://` references, and submits the
7
+ * `{ resources, prune }` bundle. `--data`/stdin still take a pre-built bundle for scripting.
8
+ *
9
+ * `apply` previews the diff and asks to confirm before writing (config-managed resources absent
10
+ * from the bundle are deleted unless `--no-prune`), so a destructive apply is never a surprise.
11
+ * These replace the raw generated `config diff`/`apply` (excluded in program.ts) so there's one
12
+ * obvious way to run each.
13
+ */
14
+ import { Command } from 'commander';
15
+ import type Roark from '@roarkanalytics/sdk';
16
+ import { type OutputFormat } from '../output';
17
+ export declare const registerConfigAsCodeCommands: (root: Command, binaryName: string, clientFor: (options: never) => Roark, resolveOutput: (options: never) => {
18
+ format: OutputFormat;
19
+ color: boolean;
20
+ }) => void;
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ /**
3
+ * `roark config diff [dir]` and `roark config apply [dir]` - config as code.
4
+ *
5
+ * These wrap the generated `config.diff` / `config.apply` SDK methods with the one client-side
6
+ * step they need to be useful: building the bundle from a directory of YAML files. Point either
7
+ * at a config directory and it reads `*.yaml`, inlines `file://` references, and submits the
8
+ * `{ resources, prune }` bundle. `--data`/stdin still take a pre-built bundle for scripting.
9
+ *
10
+ * `apply` previews the diff and asks to confirm before writing (config-managed resources absent
11
+ * from the bundle are deleted unless `--no-prune`), so a destructive apply is never a surprise.
12
+ * These replace the raw generated `config diff`/`apply` (excluded in program.ts) so there's one
13
+ * obvious way to run each.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.registerConfigAsCodeCommands = void 0;
17
+ const commander_1 = require("commander");
18
+ const confirm_1 = require("../confirm");
19
+ const errors_1 = require("../errors");
20
+ const input_1 = require("../input");
21
+ const output_1 = require("../output");
22
+ const config_bundle_1 = require("../config-bundle");
23
+ const OP_MARK = { create: '+', update: '~', delete: '-', noop: ' ' };
24
+ /** One-line-per-change summary for a terminal; `apply` results also carry a status. */
25
+ const formatChanges = (data) => {
26
+ const lines = data.changes.map((change) => {
27
+ const mark = OP_MARK[String(change['op'])] ?? '?';
28
+ const status = change['status'] ? ` (${String(change['status'])})` : '';
29
+ const error = change['error'] ? ` ${String(change['error'])}` : '';
30
+ return ` ${mark} ${String(change['configKey'])}${status}${error}`;
31
+ });
32
+ const s = data.summary;
33
+ const tally = [
34
+ `${s['create'] ?? 0} to create`,
35
+ `${s['update'] ?? 0} to update`,
36
+ `${s['delete'] ?? 0} to delete`,
37
+ ...(s['failed'] === undefined ? [] : [`${s['failed']} failed`]),
38
+ ].join(', ');
39
+ return [...lines, '', tally].join('\n');
40
+ };
41
+ const changeCount = (summary) => (summary['create'] ?? 0) + (summary['update'] ?? 0) + (summary['delete'] ?? 0);
42
+ /** Resolve the bundle: a directory (build it) or a pre-built bundle via --data / piped stdin. */
43
+ const resolveBundle = async (dir, options) => {
44
+ const prune = options['prune'] === false ? false : undefined;
45
+ if (dir !== undefined) {
46
+ return (0, config_bundle_1.buildBundle)(dir, { ...(prune === undefined ? {} : { prune }) });
47
+ }
48
+ const explicit = options['data'] === undefined ? undefined : (0, input_1.readData)(options['data']);
49
+ const piped = explicit === undefined && (0, input_1.stdinIsPiped)() ?
50
+ (() => {
51
+ const raw = (0, input_1.readStdin)().trim();
52
+ return raw.length === 0 ? undefined : JSON.parse(raw);
53
+ })()
54
+ : undefined;
55
+ const body = explicit ?? piped;
56
+ if (body === undefined || typeof body !== 'object') {
57
+ throw new errors_1.UsageError('provide a config directory (e.g. `./roark`), or a bundle via --data / stdin');
58
+ }
59
+ const bundle = body;
60
+ return prune === undefined ? bundle : { ...bundle, prune };
61
+ };
62
+ const configGroup = (root) => {
63
+ const existing = root.commands.find((child) => child.name() === 'config');
64
+ if (existing)
65
+ return existing;
66
+ const created = new commander_1.Command('config').description('Config as code, and CLI settings').showHelpAfterError();
67
+ created.action(() => created.outputHelp());
68
+ root.addCommand(created);
69
+ return created;
70
+ };
71
+ const withCommonOptions = (command) => command
72
+ .option('--data <json>', 'pre-built bundle as JSON, or @file (@- for stdin); alternative to a directory')
73
+ .option('--no-prune', 'do not delete config-managed resources absent from the bundle')
74
+ .option('--base-url <url>', 'API base URL')
75
+ .option('--token <token>', 'bearer token')
76
+ .option('--timeout <ms>', 'request timeout in milliseconds')
77
+ .option('--max-retries <count>', 'retries for retryable failures')
78
+ .option('--format <format>', 'output format')
79
+ .option('--json', 'shorthand for --format json')
80
+ .option('--no-color', 'disable colour')
81
+ .showHelpAfterError();
82
+ const registerConfigAsCodeCommands = (root, binaryName, clientFor, resolveOutput) => {
83
+ const config = configGroup(root);
84
+ withCommonOptions(config.command('diff [dir]'))
85
+ .description('Preview the changes a config directory (or bundle) would make. No writes.')
86
+ .addHelpText('after', ['', 'Examples:', ` ${binaryName} config diff ./roark`].join('\n'))
87
+ .action(async (dir, options) => {
88
+ const bundle = await resolveBundle(dir, options);
89
+ const client = clientFor(options);
90
+ const result = await client.config.diff(bundle);
91
+ const output = resolveOutput(options);
92
+ (0, output_1.write)(output.format === 'json' || options['json'] === true ?
93
+ (0, output_1.render)(result.data, output)
94
+ : formatChanges(result.data));
95
+ });
96
+ withCommonOptions(config.command('apply [dir]'))
97
+ .description('Reconcile the project to a config directory (or bundle). Previews and confirms first.')
98
+ .option('-y, --yes', 'skip the confirmation prompt')
99
+ .option('--no-input', 'never prompt; fail instead')
100
+ .addHelpText('after', ['', 'Examples:', ` ${binaryName} config apply ./roark`].join('\n'))
101
+ .action(async (dir, options) => {
102
+ const bundle = await resolveBundle(dir, options);
103
+ const client = clientFor(options);
104
+ const output = resolveOutput(options);
105
+ const json = output.format === 'json' || options['json'] === true;
106
+ // Preview then confirm, so a prune-driven delete is never a surprise.
107
+ const preview = (await client.config.diff(bundle));
108
+ const pending = changeCount(preview.data.summary);
109
+ if (pending === 0) {
110
+ (0, output_1.write)('No changes; project already matches the config.', process.stderr);
111
+ return;
112
+ }
113
+ if (!json)
114
+ (0, output_1.write)(formatChanges(preview.data), process.stderr);
115
+ await (0, confirm_1.confirm)(`Apply ${pending} change(s)?`, {
116
+ assumeYes: options['yes'] === true,
117
+ noInput: options['input'] === false,
118
+ });
119
+ const result = await client.config.apply(bundle);
120
+ (0, output_1.write)(json ?
121
+ (0, output_1.render)(result.data, output)
122
+ : formatChanges(result.data));
123
+ });
124
+ config.addHelpText('after', [
125
+ '',
126
+ 'Config as code: keep your Roark resources as YAML in git and sync them.',
127
+ ` ${binaryName} config diff ./roark # preview`,
128
+ ` ${binaryName} config apply ./roark # reconcile (asks to confirm)`,
129
+ ].join('\n'));
130
+ };
131
+ exports.registerConfigAsCodeCommands = registerConfigAsCodeCommands;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Build a config-as-code bundle from a directory of YAML files.
3
+ *
4
+ * This is the one client-side step: read `*.yaml` / `*.yml` under a directory, parse each, inline
5
+ * any `file://` references, and collect them into the `{ resources, prune }` body the API's
6
+ * `config diff` / `config apply` endpoints take. The server validates and reconciles — so this
7
+ * loader stays deliberately schema-agnostic (no per-kind knowledge), and any drift is caught by
8
+ * the API, not silently accepted here.
9
+ */
10
+ export interface ConfigBundle {
11
+ resources: Array<Record<string, unknown>>;
12
+ prune?: boolean;
13
+ }
14
+ /**
15
+ * Load every resource YAML under `dir` into a bundle. Files are read in sorted order for a
16
+ * deterministic result; each must be a single mapping with a `kind` and a `name`. Duplicate
17
+ * `kind/name` pairs are rejected up front (the server would reject them too, less clearly).
18
+ */
19
+ export declare const buildBundle: (dir: string, options?: {
20
+ prune?: boolean;
21
+ }) => Promise<ConfigBundle>;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /**
3
+ * Build a config-as-code bundle from a directory of YAML files.
4
+ *
5
+ * This is the one client-side step: read `*.yaml` / `*.yml` under a directory, parse each, inline
6
+ * any `file://` references, and collect them into the `{ resources, prune }` body the API's
7
+ * `config diff` / `config apply` endpoints take. The server validates and reconciles — so this
8
+ * loader stays deliberately schema-agnostic (no per-kind knowledge), and any drift is caught by
9
+ * the API, not silently accepted here.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.buildBundle = void 0;
13
+ const promises_1 = require("node:fs/promises");
14
+ const node_path_1 = require("node:path");
15
+ const yaml_1 = require("yaml");
16
+ const errors_1 = require("./errors");
17
+ const FILE_PREFIX = 'file://';
18
+ /**
19
+ * Resolve a single `file://relative/path` reference against the config root and return the file's
20
+ * contents. Absolute paths and any path that escapes the root are rejected — a config repo should
21
+ * only ever inline files it ships.
22
+ */
23
+ const readFileRef = async (value, root) => {
24
+ const rel = value.slice(FILE_PREFIX.length);
25
+ const target = (0, node_path_1.isAbsolute)(rel) ? rel : (0, node_path_1.join)(root, rel);
26
+ if ((0, node_path_1.relative)(root, target).startsWith('..')) {
27
+ throw new errors_1.UsageError(`prompt path escapes the config directory: ${value}`);
28
+ }
29
+ return (0, promises_1.readFile)(target, 'utf8');
30
+ };
31
+ /**
32
+ * Deep-walk a parsed resource and replace every string that begins with `file://` with the
33
+ * referenced file's contents. Kind-agnostic: it inlines a `file://` wherever it appears (prompts,
34
+ * backstories, step content, …) so the loader needs no schema.
35
+ */
36
+ const inlineFileRefs = async (value, root) => {
37
+ if (typeof value === 'string') {
38
+ return value.startsWith(FILE_PREFIX) ? readFileRef(value, root) : value;
39
+ }
40
+ if (Array.isArray(value)) {
41
+ return Promise.all(value.map((item) => inlineFileRefs(item, root)));
42
+ }
43
+ if (value && typeof value === 'object') {
44
+ const entries = await Promise.all(Object.entries(value).map(async ([key, val]) => [key, await inlineFileRefs(val, root)]));
45
+ return Object.fromEntries(entries);
46
+ }
47
+ return value;
48
+ };
49
+ /**
50
+ * Load every resource YAML under `dir` into a bundle. Files are read in sorted order for a
51
+ * deterministic result; each must be a single mapping with a `kind` and a `name`. Duplicate
52
+ * `kind/name` pairs are rejected up front (the server would reject them too, less clearly).
53
+ */
54
+ const buildBundle = async (dir, options = {}) => {
55
+ const root = (0, node_path_1.resolve)(dir);
56
+ let entries;
57
+ try {
58
+ entries = (await (0, promises_1.readdir)(root, { recursive: true })).filter((rel) => rel.endsWith('.yaml') || rel.endsWith('.yml'));
59
+ }
60
+ catch {
61
+ throw new errors_1.UsageError(`config directory not found: ${dir}`);
62
+ }
63
+ entries.sort();
64
+ const resources = [];
65
+ const seen = new Map();
66
+ for (const rel of entries) {
67
+ if (rel.endsWith('roark.lock.json'))
68
+ continue;
69
+ let raw;
70
+ try {
71
+ raw = (0, yaml_1.parse)(await (0, promises_1.readFile)((0, node_path_1.join)(root, rel), 'utf8'));
72
+ }
73
+ catch (error) {
74
+ throw new errors_1.UsageError(`${rel}: invalid YAML: ${error.message}`);
75
+ }
76
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
77
+ continue;
78
+ const resource = (await inlineFileRefs(raw, root));
79
+ const kind = resource['kind'];
80
+ const name = resource['name'];
81
+ if (typeof kind !== 'string' || typeof name !== 'string') {
82
+ throw new errors_1.UsageError(`${rel}: every resource needs a string 'kind' and 'name'`);
83
+ }
84
+ const key = `${kind}/${name}`;
85
+ const prior = seen.get(key);
86
+ if (prior)
87
+ throw new errors_1.UsageError(`${rel}: duplicate ${key} (also defined in ${prior})`);
88
+ seen.set(key, rel);
89
+ resources.push(resource);
90
+ }
91
+ if (resources.length === 0)
92
+ throw new errors_1.UsageError(`no config resources (*.yaml) found under ${dir}`);
93
+ return { resources, ...(options.prune === undefined ? {} : { prune: options.prune }) };
94
+ };
95
+ exports.buildBundle = buildBundle;
@@ -14,6 +14,7 @@ const commander_1 = require("commander");
14
14
  const sdk_1 = __importDefault(require("@roarkanalytics/sdk"));
15
15
  const api_1 = require("./commands/api");
16
16
  const auth_1 = require("./commands/auth");
17
+ const config_as_code_1 = require("./commands/config-as-code");
17
18
  const config_1 = require("./commands/config");
18
19
  const completion_1 = require("./commands/completion");
19
20
  const confirm_1 = require("./confirm");
@@ -249,9 +250,16 @@ const createProgram = (options) => {
249
250
  .showHelpAfterError()
250
251
  .enablePositionalOptions();
251
252
  addGlobalOptions(root, { body: false, confirmation: false });
252
- for (const definition of options.commands)
253
+ // `config diff` / `config apply` are hand-written (registerConfigAsCodeCommands) so they can take
254
+ // a config directory and confirm before writing; skip the raw generated versions of those two.
255
+ const isConfigAsCode = (path) => path.length === 2 && path[0] === 'config' && (path[1] === 'diff' || path[1] === 'apply');
256
+ for (const definition of options.commands) {
257
+ if (isConfigAsCode(definition.commandPath))
258
+ continue;
253
259
  addApiCommand(root, options, definition);
260
+ }
254
261
  (0, auth_1.registerAuthCommands)(root, options.binaryName);
262
+ (0, config_as_code_1.registerConfigAsCodeCommands)(root, options.binaryName, clientFor, exports.resolveOutput);
255
263
  (0, config_1.registerConfigCommands)(root, options.binaryName);
256
264
  (0, api_1.registerApiCommand)(root, options.binaryName, clientFor, exports.resolveOutput);
257
265
  (0, completion_1.registerCompletionCommand)(root, options.binaryName, options.completions);
package/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "2.31.0";
1
+ export declare const version = "0.2.0";
package/version.js CHANGED
@@ -1,8 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.version = void 0;
4
- // Bumped by hand, together with `version` in package.json. This package is not
5
- // in release-please-config.json's `extra-files`: the CLI ships from
6
- // publish-cli.yml rather than the vendor's release train, so no tooling rewrites
7
- // this line.
8
- exports.version = '2.31.0';
4
+ // Generated by scripts/sync-version.cjs from this package's package.json, which
5
+ // the build runs before tsc. Do not edit: an edit here is overwritten, and the
6
+ // published number is whatever package.json says.
7
+ //
8
+ // It exists at all because the CLI is not in release-please-config.json's
9
+ // `extra-files` - it ships from publish-cli.yml rather than the vendor's
10
+ // release train - so nothing else keeps this in step.
11
+ exports.version = '0.2.0';