bdy 1.23.3-dev → 1.23.4-dev-target-commands

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/distTs/detect-rules.json +351 -0
  2. package/distTs/package.json +3 -1
  3. package/distTs/src/api/client.js +156 -0
  4. package/distTs/src/cliIndex.js +4 -0
  5. package/distTs/src/command/artifact/list.js +1 -6
  6. package/distTs/src/command/distro/route/update.js +1 -1
  7. package/distTs/src/command/distro/update.js +9 -9
  8. package/distTs/src/command/environment/create.js +70 -0
  9. package/distTs/src/command/environment/delete.js +29 -0
  10. package/distTs/src/command/environment/get.js +48 -0
  11. package/distTs/src/command/environment/list.js +44 -0
  12. package/distTs/src/command/environment/resolve.js +22 -0
  13. package/distTs/src/command/environment/update.js +59 -0
  14. package/distTs/src/command/environment.js +20 -0
  15. package/distTs/src/command/pipeline/run/apply.js +62 -0
  16. package/distTs/src/command/target/create.js +33 -0
  17. package/distTs/src/command/target/delete.js +30 -0
  18. package/distTs/src/command/target/exec/command.js +65 -0
  19. package/distTs/src/command/target/exec/kill.js +25 -0
  20. package/distTs/src/command/target/exec/list.js +54 -0
  21. package/distTs/src/command/target/exec/logs.js +25 -0
  22. package/distTs/src/command/target/exec/status.js +41 -0
  23. package/distTs/src/command/target/exec.js +24 -0
  24. package/distTs/src/command/target/get.js +62 -0
  25. package/distTs/src/command/target/list.js +48 -0
  26. package/distTs/src/command/target/scope.js +60 -0
  27. package/distTs/src/command/target/update.js +31 -0
  28. package/distTs/src/command/target.js +22 -0
  29. package/distTs/src/command/yaml/actions/detect.js +268 -0
  30. package/distTs/src/command/yaml/actions/info.js +56 -0
  31. package/distTs/src/command/yaml/actions/list.js +70 -0
  32. package/distTs/src/command/yaml/actions/schema.js +104 -0
  33. package/distTs/src/command/yaml/actions.js +13 -0
  34. package/distTs/src/command/yaml/agents.js +88 -0
  35. package/distTs/src/command/yaml/cache.js +98 -0
  36. package/distTs/src/command/yaml/init.js +110 -0
  37. package/distTs/src/command/yaml/pipeline.js +42 -0
  38. package/distTs/src/command/yaml/render.js +83 -0
  39. package/distTs/src/command/yaml/schemaUtils.js +139 -0
  40. package/distTs/src/command/yaml/validate.js +259 -0
  41. package/distTs/src/command/yaml.js +18 -0
  42. package/distTs/src/diskCache.js +82 -0
  43. package/distTs/src/input.js +54 -0
  44. package/distTs/src/texts.js +196 -8
  45. package/distTs/src/utils.js +16 -2
  46. package/package.json +3 -1
  47. package/distTs/README.md +0 -181
  48. package/distTs/src/command/project/get.js +0 -18
  49. package/distTs/src/command/project/set.js +0 -31
  50. package/distTs/src/command/sandbox/get/yaml.js +0 -30
  51. package/distTs/src/command/vt/scrape.js +0 -193
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = require("fs");
7
+ const path_1 = require("path");
8
+ const utils_1 = require("../../utils");
9
+ const output_1 = __importDefault(require("../../output"));
10
+ const texts_1 = require("../../texts");
11
+ const schemaUtils_1 = require("./schemaUtils");
12
+ // Valid top-level keys the schema's PipelineYaml.properties omits (everything
13
+ // else is derived from the schema, so this only covers genuine gaps).
14
+ const SCHEMA_OMITTED_TOP_LEVEL_KEYS = ['git_config_ref'];
15
+ const LABEL_KEYS = ['action', 'target', 'name', 'key', 'title', 'trigger_condition'];
16
+ function stripRef(ref) {
17
+ return ref.replace('#/$defs/', '');
18
+ }
19
+ function prettifyKey(key) {
20
+ return key
21
+ .split('_')
22
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
23
+ .join(' ');
24
+ }
25
+ function outputErrors(result, indent) {
26
+ for (const err of result.errors) {
27
+ output_1.default.muted(`${indent}${err.path}: ${err.message}`);
28
+ }
29
+ }
30
+ // Array whose items reference a def — directly, or via a single $ref branch in
31
+ // items.oneOf (how `targets` mixes a string ref with an object form).
32
+ function itemRef(prop) {
33
+ if (!prop || prop.type !== 'array' || !prop.items)
34
+ return null;
35
+ if (prop.items.$ref)
36
+ return stripRef(prop.items.$ref);
37
+ if (Array.isArray(prop.items.oneOf)) {
38
+ const refs = prop.items.oneOf.filter((b) => b && b.$ref);
39
+ if (refs.length === 1)
40
+ return stripRef(refs[0].$ref);
41
+ }
42
+ return null;
43
+ }
44
+ function classifySections(props) {
45
+ const itemSections = [];
46
+ const objectSections = [];
47
+ const settingsProps = {};
48
+ for (const [key, prop] of Object.entries(props)) {
49
+ const ref = itemRef(prop);
50
+ if (ref) {
51
+ itemSections.push({ key, defName: ref });
52
+ }
53
+ else if (prop && prop.$ref) {
54
+ objectSections.push({ key, defName: stripRef(prop.$ref) });
55
+ }
56
+ else {
57
+ settingsProps[key] = prop;
58
+ }
59
+ }
60
+ return { itemSections, objectSections, settingsProps };
61
+ }
62
+ function discriminatorProp(def) {
63
+ return def && def.oneOf && def.discriminator ? def.discriminator.propertyName : null;
64
+ }
65
+ // Resolve a discriminated def to its concrete branch by the item's `type`, so
66
+ // AJV always validates against a single def (never a oneOf). Plain defs pass through.
67
+ function resolveDef(defs, refName, value) {
68
+ const def = defs[refName];
69
+ const prop = discriminatorProp(def);
70
+ if (!prop)
71
+ return { defName: refName };
72
+ const dv = value?.[prop];
73
+ if (dv === undefined || dv === null)
74
+ return { missingType: prop };
75
+ for (const branch of def.oneOf) {
76
+ const branchName = branch && branch.$ref ? stripRef(branch.$ref) : null;
77
+ if (!branchName)
78
+ continue;
79
+ if (defs[branchName]?.properties?.[prop]?.const === dv)
80
+ return { defName: branchName };
81
+ }
82
+ return { unknownType: String(dv), prop };
83
+ }
84
+ function itemLabel(value, index, discProp) {
85
+ let name;
86
+ for (const k of LABEL_KEYS) {
87
+ if (value && value[k]) {
88
+ name = String(value[k]);
89
+ break;
90
+ }
91
+ }
92
+ if (!name && !discProp && value && value.type)
93
+ name = String(value.type);
94
+ if (!name)
95
+ name = `#${index + 1}`;
96
+ return discProp && value && value[discProp] ? `${name} (${value[discProp]})` : name;
97
+ }
98
+ function validateItemSection(defs, items, refName) {
99
+ const discProp = discriminatorProp(defs[refName]);
100
+ let errors = 0;
101
+ for (let i = 0; i < items.length; i++) {
102
+ const item = items[i];
103
+ if (item === null || typeof item !== 'object' || Array.isArray(item)) {
104
+ output_1.default.green(` ${String(item)}`);
105
+ continue;
106
+ }
107
+ const label = itemLabel(item, i, discProp);
108
+ const resolved = resolveDef(defs, refName, item);
109
+ if ('missingType' in resolved) {
110
+ output_1.default.error(` ${label}: missing "${resolved.missingType}"`);
111
+ errors++;
112
+ continue;
113
+ }
114
+ if ('unknownType' in resolved) {
115
+ output_1.default.error(` ${label}: unknown ${resolved.prop} "${resolved.unknownType}"`);
116
+ errors++;
117
+ continue;
118
+ }
119
+ const result = (0, schemaUtils_1.validateAgainstDef)(resolved.defName, item);
120
+ if (result.valid) {
121
+ output_1.default.green(` ${label}`);
122
+ }
123
+ else {
124
+ errors += result.errors.length;
125
+ output_1.default.error(` ${label}`);
126
+ outputErrors(result, ' ');
127
+ }
128
+ }
129
+ return errors;
130
+ }
131
+ function validateObjectSection(defs, label, value, refName) {
132
+ const resolved = resolveDef(defs, refName, value);
133
+ if ('missingType' in resolved) {
134
+ output_1.default.error(label);
135
+ output_1.default.muted(` missing "${resolved.missingType}"`);
136
+ return 1;
137
+ }
138
+ if ('unknownType' in resolved) {
139
+ output_1.default.error(label);
140
+ output_1.default.muted(` unknown ${resolved.prop} "${resolved.unknownType}"`);
141
+ return 1;
142
+ }
143
+ const result = (0, schemaUtils_1.validateAgainstDef)(resolved.defName, value);
144
+ if (result.valid) {
145
+ output_1.default.green(label);
146
+ return 0;
147
+ }
148
+ output_1.default.error(label);
149
+ outputErrors(result, ' ');
150
+ return result.errors.length;
151
+ }
152
+ function validateSinglePipeline(parsed, label) {
153
+ let errors = 0;
154
+ const defs = (0, schemaUtils_1.getAllDefs)();
155
+ const pipelineDef = defs['PipelineYaml'];
156
+ const props = pipelineDef?.properties || {};
157
+ const allKnownKeys = new Set([...Object.keys(props), ...SCHEMA_OMITTED_TOP_LEVEL_KEYS]);
158
+ const unknownKeys = Object.keys(parsed).filter((k) => !allKnownKeys.has(k));
159
+ if (unknownKeys.length > 0) {
160
+ output_1.default.yellow(`${label}Unknown top-level keys: ${unknownKeys.join(', ')}`);
161
+ }
162
+ const { itemSections, objectSections, settingsProps } = classifySections(props);
163
+ if (Object.keys(settingsProps).length > 0) {
164
+ const settingsData = {};
165
+ for (const key of Object.keys(settingsProps)) {
166
+ if (key in parsed)
167
+ settingsData[key] = parsed[key];
168
+ }
169
+ const rawSchema = (0, schemaUtils_1.loadRawSchema)();
170
+ const result = (0, schemaUtils_1.validateAgainstSchema)({
171
+ type: 'object',
172
+ properties: settingsProps,
173
+ required: (pipelineDef?.required || []).filter((r) => r in settingsProps),
174
+ $defs: rawSchema.$defs,
175
+ }, settingsData);
176
+ if (result.valid) {
177
+ output_1.default.green(`${label}Pipeline settings`);
178
+ }
179
+ else {
180
+ errors += result.errors.length;
181
+ output_1.default.error(`${label}Pipeline settings`);
182
+ outputErrors(result, ' ');
183
+ }
184
+ }
185
+ const itemByKey = new Map(itemSections.map((s) => [s.key, s.defName]));
186
+ const objByKey = new Map(objectSections.map((s) => [s.key, s.defName]));
187
+ for (const key of Object.keys(props)) {
188
+ if (!(key in parsed))
189
+ continue;
190
+ const value = parsed[key];
191
+ const header = `${label}${prettifyKey(key)}`;
192
+ if (itemByKey.has(key)) {
193
+ if (!Array.isArray(value))
194
+ continue;
195
+ output_1.default.blue(header);
196
+ errors += validateItemSection(defs, value, itemByKey.get(key));
197
+ }
198
+ else if (objByKey.has(key)) {
199
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
200
+ continue;
201
+ errors += validateObjectSection(defs, header, value, objByKey.get(key));
202
+ }
203
+ }
204
+ return errors;
205
+ }
206
+ function validatePipelineFile(file) {
207
+ const filePath = (0, path_1.resolve)(file);
208
+ if (!(0, fs_1.existsSync)(filePath)) {
209
+ output_1.default.exitError((0, texts_1.ERR_YAML_VALIDATE_FILE_NOT_FOUND)(file));
210
+ return;
211
+ }
212
+ const YAML = require('yaml');
213
+ const content = (0, fs_1.readFileSync)(filePath, 'utf8');
214
+ let parsed;
215
+ try {
216
+ parsed = YAML.parse(content);
217
+ }
218
+ catch (e) {
219
+ output_1.default.exitError(`YAML parse error: ${e.message}`);
220
+ return;
221
+ }
222
+ if (!parsed || typeof parsed !== 'object') {
223
+ output_1.default.exitError('Invalid pipeline YAML: expected an array of pipeline objects');
224
+ return;
225
+ }
226
+ if (!Array.isArray(parsed)) {
227
+ output_1.default.exitError('Invalid pipeline YAML: expected an array (e.g. "- pipeline: my-pipeline"), got a plain object');
228
+ return;
229
+ }
230
+ if (parsed.length === 0) {
231
+ output_1.default.exitError('Invalid pipeline YAML: empty array');
232
+ return;
233
+ }
234
+ let errors = 0;
235
+ for (let i = 0; i < parsed.length; i++) {
236
+ const pipeline = parsed[i];
237
+ if (!pipeline || typeof pipeline !== 'object' || Array.isArray(pipeline)) {
238
+ output_1.default.error(` Pipeline #${i + 1}: expected an object`);
239
+ errors++;
240
+ continue;
241
+ }
242
+ const pipelineLabel = parsed.length > 1 ? `[${pipeline.pipeline || i + 1}] ` : '';
243
+ errors += validateSinglePipeline(pipeline, pipelineLabel);
244
+ }
245
+ output_1.default.normal('');
246
+ if (errors > 0) {
247
+ output_1.default.exitError(`${texts_1.ERR_YAML_VALIDATE_FAILED} (${errors} ${errors === 1 ? 'error' : 'errors'})`);
248
+ }
249
+ else {
250
+ output_1.default.exitSuccess(texts_1.TXT_YAML_VALIDATE_ALL_PASS);
251
+ }
252
+ }
253
+ const commandYamlValidate = (0, utils_1.newCommand)('validate', texts_1.DESC_COMMAND_YAML_VALIDATE);
254
+ commandYamlValidate.argument('<file>', texts_1.OPTION_YAML_VALIDATE_FILE);
255
+ commandYamlValidate.action(async (file) => {
256
+ await (0, schemaUtils_1.ensureYamlSchema)();
257
+ validatePipelineFile(file);
258
+ });
259
+ exports.default = commandYamlValidate;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const utils_1 = require("../utils");
7
+ const texts_1 = require("../texts");
8
+ const pipeline_1 = __importDefault(require("./yaml/pipeline"));
9
+ const actions_1 = __importDefault(require("./yaml/actions"));
10
+ const validate_1 = __importDefault(require("./yaml/validate"));
11
+ const init_1 = __importDefault(require("./yaml/init"));
12
+ const commandYaml = (0, utils_1.newCommand)('yaml', texts_1.DESC_COMMAND_YAML);
13
+ commandYaml.addCommand(pipeline_1.default);
14
+ commandYaml.addCommand(actions_1.default);
15
+ commandYaml.addCommand(validate_1.default);
16
+ commandYaml.addCommand(init_1.default);
17
+ commandYaml.addHelpText('after', texts_1.EXAMPLE_YAML);
18
+ exports.default = commandYaml;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CACHE_TTL_MS = void 0;
7
+ exports.cacheRoot = cacheRoot;
8
+ exports.hashKey = hashKey;
9
+ exports.cachePaths = cachePaths;
10
+ exports.readMeta = readMeta;
11
+ exports.isHardFresh = isHardFresh;
12
+ exports.writeAtomic = writeAtomic;
13
+ exports.refresh = refresh;
14
+ const node_crypto_1 = require("node:crypto");
15
+ const node_fs_1 = require("node:fs");
16
+ const node_path_1 = __importDefault(require("node:path"));
17
+ const undici_1 = require("undici");
18
+ const utils_1 = require("./utils");
19
+ exports.CACHE_TTL_MS = 60 * 60 * 1000; // 1h
20
+ function cacheRoot() {
21
+ const dir = node_path_1.default.join((0, utils_1.getHomeDirectory)(), 'cache');
22
+ if (!(0, node_fs_1.existsSync)(dir))
23
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
24
+ return dir;
25
+ }
26
+ function hashKey(input) {
27
+ return (0, node_crypto_1.createHash)('sha256').update(input).digest('hex').slice(0, 16);
28
+ }
29
+ function cachePaths(subdir, url) {
30
+ const dir = node_path_1.default.join(cacheRoot(), subdir);
31
+ if (!(0, node_fs_1.existsSync)(dir))
32
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
33
+ const key = hashKey(url);
34
+ return {
35
+ raw: node_path_1.default.join(dir, `${key}.json`),
36
+ schema: node_path_1.default.join(dir, `${key}.v8`),
37
+ meta: node_path_1.default.join(dir, `${key}.meta.json`),
38
+ };
39
+ }
40
+ function readMeta(metaPath) {
41
+ try {
42
+ return JSON.parse((0, node_fs_1.readFileSync)(metaPath, 'utf8'));
43
+ }
44
+ catch {
45
+ return {};
46
+ }
47
+ }
48
+ function isHardFresh(meta, ttlMs = exports.CACHE_TTL_MS) {
49
+ return !!meta.fetchedAt && Date.now() - meta.fetchedAt < ttlMs;
50
+ }
51
+ function writeAtomic(filePath, data) {
52
+ const tmp = `${filePath}.${process.pid}.tmp`;
53
+ (0, node_fs_1.writeFileSync)(tmp, data);
54
+ (0, node_fs_1.renameSync)(tmp, filePath);
55
+ }
56
+ async function refresh(url, paths) {
57
+ const hasRaw = (0, node_fs_1.existsSync)(paths.raw);
58
+ const meta = hasRaw ? readMeta(paths.meta) : {};
59
+ // The server serves a strong content-based ETag, so revalidate with If-None-Match.
60
+ const headers = {};
61
+ if (hasRaw && meta.etag)
62
+ headers['if-none-match'] = meta.etag;
63
+ try {
64
+ const res = await (0, undici_1.request)(url, { headers });
65
+ const etag = res.headers['etag'];
66
+ if (res.statusCode === 304 && hasRaw) {
67
+ await res.body.dump();
68
+ writeAtomic(paths.meta, JSON.stringify({ etag: meta.etag, fetchedAt: Date.now() }));
69
+ return 'unchanged';
70
+ }
71
+ if (res.statusCode >= 200 && res.statusCode < 300) {
72
+ writeAtomic(paths.raw, await res.body.text());
73
+ writeAtomic(paths.meta, JSON.stringify({ etag: typeof etag === 'string' ? etag : undefined, fetchedAt: Date.now() }));
74
+ return 'updated';
75
+ }
76
+ await res.body.dump();
77
+ }
78
+ catch {
79
+ // network error — fall back to the cached body if present
80
+ }
81
+ return hasRaw ? 'unchanged' : 'none';
82
+ }
@@ -1087,6 +1087,54 @@ class Input {
1087
1087
  return distro_1.SCOPE.PROJECT;
1088
1088
  return distro_1.SCOPE.WORKSPACE;
1089
1089
  }
1090
+ static targetScope(project, pipeline, environment, scope) {
1091
+ Input.targetLine(project, pipeline, environment);
1092
+ if (scope) {
1093
+ scope = scope.toUpperCase();
1094
+ if (scope === utils_1.TARGET_SCOPE.WORKSPACE) {
1095
+ return scope;
1096
+ }
1097
+ if (scope === utils_1.TARGET_SCOPE.PROJECT) {
1098
+ if (!project)
1099
+ output_1.default.exitError(texts_1.ERR_COMMAND_SCOPE_NO_PROJECT);
1100
+ return scope;
1101
+ }
1102
+ if (scope === utils_1.TARGET_SCOPE.PIPELINE) {
1103
+ if (!pipeline)
1104
+ output_1.default.exitError(texts_1.ERR_TARGET_SCOPE_NO_PIPELINE);
1105
+ return scope;
1106
+ }
1107
+ if (scope === utils_1.TARGET_SCOPE.ENVIRONMENT) {
1108
+ if (!environment)
1109
+ output_1.default.exitError(texts_1.ERR_TARGET_SCOPE_NO_ENVIRONMENT);
1110
+ return scope;
1111
+ }
1112
+ return output_1.default.exitError(texts_1.ERR_TARGET_SCOPE);
1113
+ }
1114
+ if (pipeline)
1115
+ return utils_1.TARGET_SCOPE.PIPELINE;
1116
+ if (environment)
1117
+ return utils_1.TARGET_SCOPE.ENVIRONMENT;
1118
+ if (project)
1119
+ return utils_1.TARGET_SCOPE.PROJECT;
1120
+ return utils_1.TARGET_SCOPE.WORKSPACE;
1121
+ }
1122
+ // Line context for resolving a target identifier via /identifiers:
1123
+ // params describe one line from workspace down (ws -> project -> pipeline
1124
+ // or environment), searched from the deepest scope up.
1125
+ static targetLine(project, pipeline, environment) {
1126
+ if (pipeline && !project) {
1127
+ output_1.default.exitError(texts_1.ERR_TARGET_PIPELINE_REQUIRES_PROJECT);
1128
+ }
1129
+ if (pipeline && environment) {
1130
+ output_1.default.exitError(texts_1.ERR_TARGET_PIPELINE_ENV_CONFLICT);
1131
+ }
1132
+ return {
1133
+ project: project || undefined,
1134
+ pipeline,
1135
+ environment,
1136
+ };
1137
+ }
1090
1138
  static restApiProject(project, allowNull) {
1091
1139
  const ProjectCfg = require('./project/cfg').default;
1092
1140
  let p = process.env.BUDDY_PROJECT;
@@ -1107,6 +1155,12 @@ class Input {
1107
1155
  output_1.default.exitError(texts_1.ERR_SANDBOX_BOOT_SCRIPT_NOT_EXISTS);
1108
1156
  return node_fs_1.default.readFileSync(p, 'utf8');
1109
1157
  }
1158
+ static tags(tags) {
1159
+ return tags
1160
+ .split(',')
1161
+ .map((t) => t.trim())
1162
+ .filter(Boolean);
1163
+ }
1110
1164
  static restApiYaml(yaml) {
1111
1165
  if (!yaml)
1112
1166
  output_1.default.exitError(texts_1.ERR_REST_API_YAML);