@devicecloud.dev/dcd 5.5.0-beta.5 → 5.6.0-beta.3

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.
@@ -402,15 +402,22 @@ export const cloudCommand = defineCommand({
402
402
  }
403
403
  throw error;
404
404
  }
405
- const { allExcludeTags, allIncludeTags, flowMetadata, flowOverrides, flowsToRun: testFileNames, referencedFiles, sequence, } = executionPlan;
405
+ const { allExcludeTags, allIncludeTags, flowMetadata, flowOverrides, flowsToRun: testFileNames, includedFiles, referencedFiles, sequence, } = executionPlan;
406
406
  if (debug) {
407
407
  out(`[DEBUG] All include tags: ${allIncludeTags?.join(', ') || 'none'}`);
408
408
  out(`[DEBUG] All exclude tags: ${allExcludeTags?.join(', ') || 'none'}`);
409
409
  out(`[DEBUG] Test file names: ${testFileNames.join(', ')}`);
410
410
  }
411
- const commonRoot = computeCommonRoot(testFileNames, referencedFiles);
411
+ const commonRoot = computeCommonRoot(testFileNames, referencedFiles, includedFiles);
412
412
  if (debug) {
413
413
  out(`[DEBUG] Common root directory: ${commonRoot}`);
414
+ // `includedPaths` files sitting beside the flows tree rather than
415
+ // inside it raise the common root, so every server-side flow key gains
416
+ // a leading segment. Harmless but visible in the console, so say it.
417
+ const rootWithoutIncludes = computeCommonRoot(testFileNames, referencedFiles);
418
+ if (includedFiles.length > 0 && rootWithoutIncludes !== commonRoot) {
419
+ out(`[DEBUG] \`includedPaths\` raised the common root from ${rootWithoutIncludes} to ${commonRoot} — flow paths gain a leading segment`);
420
+ }
414
421
  }
415
422
  const testMetadataMap = buildTestMetadataMap(flowMetadata, commonRoot);
416
423
  if (debug) {
@@ -120,7 +120,7 @@ export function registerRunCloudTest(server) {
120
120
  // stdout is the JSON-RPC channel — config warnings must go to stderr.
121
121
  warn: logStderr,
122
122
  });
123
- const commonRoot = computeCommonRoot(executionPlan.flowsToRun, executionPlan.referencedFiles);
123
+ const commonRoot = computeCommonRoot(executionPlan.flowsToRun, executionPlan.referencedFiles, executionPlan.includedFiles);
124
124
  const testMetadataMap = buildTestMetadataMap(executionPlan.flowMetadata, commonRoot);
125
125
  if (args.dryRun) {
126
126
  return jsonResult({
@@ -21,6 +21,12 @@ export interface IExecutionPlan {
21
21
  flowMetadata: Record<string, Record<string, unknown>>;
22
22
  flowOverrides: Record<string, Record<string, unknown>>;
23
23
  flowsToRun: string[];
24
+ /**
25
+ * Extra files pulled in by `config.yaml`'s `includedPaths`. Kept separate
26
+ * from `referencedFiles` (which is derived from flow commands) so the zip
27
+ * manifest and the common-root calculation can tell the two apart.
28
+ */
29
+ includedFiles: string[];
24
30
  referencedFiles: string[];
25
31
  sequence?: IFlowSequence | null;
26
32
  totalFlowFiles: number;
@@ -90,7 +90,7 @@ function extractDeviceCloudOverrides(config) {
90
90
  * @param resolvedConfigFile - Optional absolute path to a custom config file
91
91
  * @returns Execution plan for the single file with dependencies
92
92
  */
93
- async function planSingleFile(normalizedInput, warn, resolvedConfigFile) {
93
+ async function planSingleFile(normalizedInput, warn, resolvedConfigFile, debug = false) {
94
94
  const inputBasename = path.basename(normalizedInput);
95
95
  if (inputBasename === 'config.yaml' ||
96
96
  inputBasename === 'config.yml' ||
@@ -118,11 +118,18 @@ async function planSingleFile(normalizedInput, warn, resolvedConfigFile) {
118
118
  `Pass the workspace folder instead so the named flows can be discovered and sequenced.`);
119
119
  }
120
120
  }
121
+ // A single-file input has no workspace directory, so `includedPaths` (which
122
+ // only reaches here via --config) anchors on the flow file's own directory —
123
+ // the same place Maestro resolves an assertScreenshot baseline from.
124
+ const includedFiles = workspaceConfig
125
+ ? resolveIncludedPaths(workspaceConfig, path.dirname(normalizedInput), warn, debug)
126
+ : [];
121
127
  const checkedDependancies = await checkDependencies(normalizedInput);
122
128
  return {
123
129
  flowMetadata,
124
130
  flowOverrides,
125
131
  flowsToRun: [normalizedInput],
132
+ includedFiles,
126
133
  referencedFiles: [...new Set(checkedDependancies)],
127
134
  totalFlowFiles: 1,
128
135
  workspaceConfig,
@@ -176,6 +183,94 @@ async function applyFlowGlobs(workspaceConfig, normalizedInput, unfilteredFlowFi
176
183
  }
177
184
  return unfilteredFlowFiles.filter((file) => !isExcludedConfig(file));
178
185
  }
186
+ /**
187
+ * The whole archive is buffered in memory by `compressFilesFromRelativePath`,
188
+ * so an unbounded `**` glob is a real footgun. These are warn thresholds, not
189
+ * hard limits — a legitimately large baseline set should still upload.
190
+ */
191
+ const INCLUDED_PATHS_FILE_WARN_THRESHOLD = 200;
192
+ const INCLUDED_PATHS_BYTES_WARN_THRESHOLD = 50 * 1024 * 1024;
193
+ /**
194
+ * Resolve `config.yaml`'s `includedPaths` globs into absolute file paths.
195
+ *
196
+ * This is the general-purpose escape hatch for shipping files the flow
197
+ * commands don't reference: `assertScreenshot` baselines above all, but also
198
+ * fixtures, test data and certificates. Only `addMedia` / `runFlow` /
199
+ * `runScript` arguments are discovered by walking the flows, so without this
200
+ * key such files are silently absent from the uploaded zip.
201
+ *
202
+ * Glob semantics deliberately mirror `flows:` (`applyFlowGlobs`): patterns
203
+ * resolve against the workspace root, never the config file's directory, so
204
+ * `--config ci/workspace.yaml` behaves identically to an auto-detected config.
205
+ *
206
+ * @param workspaceConfig - Validated workspace config
207
+ * @param normalizedInput - Normalized path to the workspace directory
208
+ * @param warn - Sink for non-fatal problems
209
+ * @param debug - Whether to emit debug logging
210
+ * @returns Absolute paths of every matched file, deduped and sorted
211
+ * @throws Error if a pattern escapes the workspace root
212
+ */
213
+ function resolveIncludedPaths(workspaceConfig, normalizedInput, warn, debug = false) {
214
+ const patterns = workspaceConfig.includedPaths;
215
+ if (!patterns || patterns.length === 0)
216
+ return [];
217
+ const workspaceRoot = path.resolve(normalizedInput);
218
+ const resolved = new Set();
219
+ const unmatched = [];
220
+ for (const pattern of patterns) {
221
+ // fs.globSync lands in Node 22; the CLI's `engines.node` already requires
222
+ // it. No `nodir` option — directories are stripped by the stat check below.
223
+ const matches = fs.globSync(pattern, { cwd: normalizedInput });
224
+ let matchedFile = false;
225
+ for (const match of matches) {
226
+ const absolute = path.resolve(normalizedInput, match);
227
+ // Containment guard: `flows:` has none because its matches are only ever
228
+ // parsed as YAML, but this key ships arbitrary bytes to a remote runner,
229
+ // so a `../../../` climb must not silently leave the workspace.
230
+ const relative = path.relative(workspaceRoot, absolute);
231
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
232
+ throw new Error(`\`includedPaths\` pattern "${pattern}" resolves outside the workspace: ${absolute}\n\n` +
233
+ `Included paths must stay within ${workspaceRoot}.`);
234
+ }
235
+ try {
236
+ if (!fs.statSync(absolute).isFile())
237
+ continue;
238
+ }
239
+ catch {
240
+ continue;
241
+ }
242
+ matchedFile = true;
243
+ resolved.add(absolute);
244
+ }
245
+ if (!matchedFile)
246
+ unmatched.push(pattern);
247
+ }
248
+ if (unmatched.length > 0) {
249
+ warn(`Warning: \`includedPaths\` pattern(s) in config matched no files:\n` +
250
+ `${unmatched.map((pattern) => ` ${pattern}`).join('\n')}\n\n` +
251
+ `Patterns are resolved relative to ${workspaceRoot}.`);
252
+ }
253
+ const files = [...resolved].sort((a, b) => a.localeCompare(b));
254
+ let totalBytes = 0;
255
+ for (const file of files) {
256
+ try {
257
+ totalBytes += fs.statSync(file).size;
258
+ }
259
+ catch {
260
+ // Raced away between glob and stat; the zip step reports it properly.
261
+ }
262
+ }
263
+ if (files.length > INCLUDED_PATHS_FILE_WARN_THRESHOLD ||
264
+ totalBytes > INCLUDED_PATHS_BYTES_WARN_THRESHOLD) {
265
+ warn(`Warning: \`includedPaths\` matched ${files.length} file(s) totalling ` +
266
+ `${Math.round(totalBytes / (1024 * 1024))} MB. The flow archive is built in ` +
267
+ `memory, so consider narrowing the patterns.`);
268
+ }
269
+ if (debug) {
270
+ console.log(`[DEBUG] includedPaths matched ${files.length} file(s):\n${files.join('\n')}`);
271
+ }
272
+ return files;
273
+ }
179
274
  /**
180
275
  * Resolve sequential execution order from workspace config
181
276
  * @param workspaceConfig - Workspace configuration with executionOrder
@@ -241,7 +336,7 @@ export async function plan(options) {
241
336
  throw new Error(`Flow path does not exist: ${path.resolve(normalizedInput)}`);
242
337
  }
243
338
  if (fs.lstatSync(normalizedInput).isFile()) {
244
- return planSingleFile(normalizedInput, warn, resolvedConfigFile);
339
+ return planSingleFile(normalizedInput, warn, resolvedConfigFile, debug);
245
340
  }
246
341
  let unfilteredFlowFiles = await readDirectory(normalizedInput, isFlowFile);
247
342
  if (unfilteredFlowFiles.length === 0) {
@@ -325,6 +420,7 @@ export async function plan(options) {
325
420
  flowMetadata,
326
421
  flowOverrides,
327
422
  flowsToRun: normalFlows,
423
+ includedFiles: resolveIncludedPaths(workspaceConfig, normalizedInput, warn, debug),
328
424
  referencedFiles: [...new Set(allFiles)],
329
425
  sequence: {
330
426
  continueOnFailure: workspaceConfig.executionOrder?.continueOnFailure,
@@ -2,7 +2,50 @@ import * as yaml from 'js-yaml';
2
2
  import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
4
  import { parseWorkspaceConfig, WORKSPACE_CONFIG_KEYS, } from './workspace-config.schema.js';
5
- const commandsThatRequireFiles = new Set(['addMedia', 'runFlow', 'runScript']);
5
+ const commandsThatRequireFiles = new Set([
6
+ 'addMedia',
7
+ 'assertScreenshot',
8
+ 'runFlow',
9
+ 'runScript',
10
+ ]);
11
+ /**
12
+ * Commands whose file references are best-effort rather than mandatory.
13
+ *
14
+ * `assertScreenshot` baselines are legitimately absent on a first run, and
15
+ * Maestro's own "Screenshot file not found — searched in: …" error is more
16
+ * useful than ours, so a missing baseline must not abort the upload the way a
17
+ * missing `addMedia` file does.
18
+ */
19
+ const commandsWithOptionalFiles = new Set(['assertScreenshot']);
20
+ /**
21
+ * Extensions Maestro's `normalizeScreenshotPath` recognises; anything else
22
+ * gets `.png` appended, so `assertScreenshot: home` means `home.png`.
23
+ */
24
+ const SCREENSHOT_EXTENSIONS = new Set([
25
+ '.bmp',
26
+ '.gif',
27
+ '.heic',
28
+ '.heif',
29
+ '.jpeg',
30
+ '.jpg',
31
+ '.png',
32
+ '.tiff',
33
+ '.wbmp',
34
+ ]);
35
+ /**
36
+ * Mirror Maestro's `Orchestra.normalizeScreenshotPath`: a screenshot path with
37
+ * no image extension gets `.png`. Without this, `assertScreenshot: home` looks
38
+ * like a missing file here while resolving fine on the device.
39
+ *
40
+ * @param relativePath - The path as written in the flow
41
+ * @returns The path with an image extension guaranteed
42
+ */
43
+ function normalizeScreenshotPath(relativePath) {
44
+ const extension = path.extname(relativePath).toLowerCase();
45
+ return SCREENSHOT_EXTENSIONS.has(extension)
46
+ ? relativePath
47
+ : `${relativePath}.png`;
48
+ }
6
49
  export function getFlowsToRunInSequence(paths, flowOrder, debug = false) {
7
50
  if (flowOrder.length === 0) {
8
51
  if (debug) {
@@ -145,12 +188,27 @@ export const checkIfFilesExistInWorkspace = (commandName, command, absoluteFileP
145
188
  const errors = [];
146
189
  const files = [];
147
190
  const directory = path.dirname(absoluteFilePath);
191
+ const isScreenshot = commandName === 'assertScreenshot';
192
+ const isOptional = commandsWithOptionalFiles.has(commandName);
148
193
  const buildError = (error) => `Flow file "${absoluteFilePath}" has a command "${commandName}" that references a ${error} ${JSON.stringify(command)}`;
149
194
  const processFilePath = (relativePath) => {
150
- const absoluteFilePath = path.normalize(path.resolve(directory, relativePath));
195
+ // A JS/variable-interpolated path (`screenshots/${DCD_DEVICE}/home`) can't
196
+ // be resolved without running the flow. Skip it rather than guessing — the
197
+ // config.yaml `includedPaths` key is how those files get bundled.
198
+ if (relativePath.includes('${'))
199
+ return;
200
+ const resolvedRelativePath = isScreenshot
201
+ ? normalizeScreenshotPath(relativePath)
202
+ : relativePath;
203
+ const absoluteFilePath = path.normalize(path.resolve(directory, resolvedRelativePath));
151
204
  const error = checkFile(absoluteFilePath);
152
- if (error)
205
+ if (error) {
206
+ // Optional references drop out entirely when missing: pushing them onto
207
+ // `files` would put a non-existent path into the zip manifest.
208
+ if (isOptional)
209
+ return;
153
210
  errors.push(buildError(error));
211
+ }
154
212
  files.push(absoluteFilePath);
155
213
  };
156
214
  // simple command — processFilePath already resolves against `directory`
@@ -163,10 +221,15 @@ export const checkIfFilesExistInWorkspace = (commandName, command, absoluteFileP
163
221
  processFilePath(file);
164
222
  }
165
223
  }
166
- // object command
224
+ // object command. `file` is addMedia/runFlow/runScript; `path` is
225
+ // assertScreenshot's own key for the same thing.
167
226
  const x = command; // prevent annoying ts error
168
- if (typeof command === 'object' && x?.file)
169
- processFilePath(x.file);
227
+ if (typeof command === 'object' && !Array.isArray(command)) {
228
+ if (x?.file)
229
+ processFilePath(x.file);
230
+ if (isScreenshot && typeof x?.path === 'string')
231
+ processFilePath(x.path);
232
+ }
170
233
  return { errors, files };
171
234
  };
172
235
  const checkFile = (filePath) => {
@@ -3,8 +3,16 @@
3
3
  * file path. Segment comparison (not `startsWith`) so sibling dirs like
4
4
  * `flows`/`flows-extra` can't merge, and the file segment itself is never
5
5
  * consumed. Returns '' when the paths share no root at all (or none are given).
6
+ *
7
+ * `includedFiles` (config.yaml `includedPaths`) must be folded in for the same
8
+ * reason referenced files are: the zip strips this root as an anchored prefix,
9
+ * so a file outside it would get a non-relative entry name. Folding them in
10
+ * can raise the root — flows in `flows/` beside baselines in `screenshots/`
11
+ * shifts it from `<root>/flows` to `<root>`, so flow keys gain a `flows/`
12
+ * segment. That shift is what preserves the flow→baseline relative offset
13
+ * Maestro resolves against, and is already how `addMedia` behaves.
6
14
  */
7
- export declare function computeCommonRoot(testFileNames: string[], referencedFiles: string[]): string;
15
+ export declare function computeCommonRoot(testFileNames: string[], referencedFiles: string[], includedFiles?: string[]): string;
8
16
  export interface FlowMetadataEntry {
9
17
  flowName: string;
10
18
  tags: string[];
@@ -12,9 +12,21 @@ import { toPortableRelativePath } from '../utils/paths.js';
12
12
  * file path. Segment comparison (not `startsWith`) so sibling dirs like
13
13
  * `flows`/`flows-extra` can't merge, and the file segment itself is never
14
14
  * consumed. Returns '' when the paths share no root at all (or none are given).
15
+ *
16
+ * `includedFiles` (config.yaml `includedPaths`) must be folded in for the same
17
+ * reason referenced files are: the zip strips this root as an anchored prefix,
18
+ * so a file outside it would get a non-relative entry name. Folding them in
19
+ * can raise the root — flows in `flows/` beside baselines in `screenshots/`
20
+ * shifts it from `<root>/flows` to `<root>`, so flow keys gain a `flows/`
21
+ * segment. That shift is what preserves the flow→baseline relative offset
22
+ * Maestro resolves against, and is already how `addMedia` behaves.
15
23
  */
16
- export function computeCommonRoot(testFileNames, referencedFiles) {
17
- const pathsShortestToLongest = [...testFileNames, ...referencedFiles].sort((a, b) => a.split(path.sep).length - b.split(path.sep).length);
24
+ export function computeCommonRoot(testFileNames, referencedFiles, includedFiles = []) {
25
+ const pathsShortestToLongest = [
26
+ ...testFileNames,
27
+ ...referencedFiles,
28
+ ...includedFiles,
29
+ ].sort((a, b) => a.split(path.sep).length - b.split(path.sep).length);
18
30
  if (pathsShortestToLongest.length === 0)
19
31
  return '';
20
32
  const splitPaths = pathsShortestToLongest.map((p) => p.split(path.sep));
@@ -57,6 +57,14 @@ export declare function deviceFromResultRow(r: {
57
57
  export declare function isSupersededRow(r: unknown): boolean;
58
58
  /** The upload that superseded this run, for the console link. */
59
59
  export declare function supersedingUploadId(results: unknown[]): string | undefined;
60
+ /**
61
+ * The superseding run's console link, derived from this run's.
62
+ *
63
+ * The `result` parameter deep-links a single test, and that id belongs to
64
+ * THIS upload — carrying it over would point at a result the newer upload
65
+ * does not contain. Swap the upload id and drop it.
66
+ */
67
+ export declare function supersedingConsoleUrl(consoleUrl: string, uploadId: string, supersededBy: string): string;
60
68
  export interface PollingResult {
61
69
  consoleUrl: string;
62
70
  status: 'FAILED' | 'PASSED' | 'SUPERSEDED';
@@ -63,6 +63,20 @@ export function supersedingUploadId(results) {
63
63
  }
64
64
  return undefined;
65
65
  }
66
+ /**
67
+ * The superseding run's console link, derived from this run's.
68
+ *
69
+ * The `result` parameter deep-links a single test, and that id belongs to
70
+ * THIS upload — carrying it over would point at a result the newer upload
71
+ * does not contain. Swap the upload id and drop it.
72
+ */
73
+ export function supersedingConsoleUrl(consoleUrl, uploadId, supersededBy) {
74
+ return consoleUrl
75
+ .replace(uploadId, supersededBy)
76
+ .replace(/&result=[^&]*/, '')
77
+ .replace(/\?result=[^&]*&/, '?')
78
+ .replace(/\?result=[^&]*$/, '');
79
+ }
66
80
  /**
67
81
  * Service for polling test results from the API
68
82
  */
@@ -438,7 +452,7 @@ export class ResultsPollingService {
438
452
  logger(ui.branch(ui.fields([
439
453
  [
440
454
  'superseded by',
441
- colors.url(consoleUrl.replace(uploadId, newer)),
455
+ colors.url(supersedingConsoleUrl(consoleUrl, uploadId, newer)),
442
456
  ],
443
457
  ])));
444
458
  }
@@ -21,7 +21,7 @@ export class TestSubmissionService {
21
21
  */
22
22
  async buildTestPayload(config) {
23
23
  const { apiUrl, appBinaryId, encrypt = false, flowFile, executionPlan, commonRoot, cliVersion, env = [], metadata = [], cancelPrevious = false, googlePlay = false, androidApiLevel, androidDevice, androidNoSnapshot, iOSVersion, iOSDevice, name, runnerType, maestroVersion, deviceLocale, orientation, retry, continueOnFailure = true, renderEngine, report, showCrosshairs, maestroChromeOnboarding, raw, disableAnimations, deviceMatrix, debug = false, logger, } = config;
24
- const { allExcludeTags, allIncludeTags, flowMetadata, flowOverrides, flowsToRun: testFileNames, referencedFiles, sequence, workspaceConfig, } = executionPlan;
24
+ const { allExcludeTags, allIncludeTags, flowMetadata, flowOverrides, flowsToRun: testFileNames, includedFiles = [], referencedFiles, sequence, workspaceConfig, } = executionPlan;
25
25
  const { flows: sequentialFlows = [] } = sequence ?? {};
26
26
  const envObject = this.parseKeyValuePairs(env);
27
27
  const metadataObject = this.parseKeyValuePairs(metadata);
@@ -42,6 +42,17 @@ export class TestSubmissionService {
42
42
  }
43
43
  }
44
44
  }
45
+ // Logged separately from referencedFiles: these come from config.yaml's
46
+ // `includedPaths` rather than from a flow command, and they can raise the
47
+ // common root (see computeCommonRoot) — which shows up here as every flow
48
+ // key gaining a leading directory segment.
49
+ if (includedFiles.length > 0) {
50
+ this.logDebug(debug, logger, `[DEBUG] Uploading ${includedFiles.length} file(s) from \`includedPaths\`:`);
51
+ for (const file of includedFiles) {
52
+ const normalizedPath = this.normalizeFilePath(file, commonRoot);
53
+ this.logDebug(debug, logger, `[DEBUG] - ${normalizedPath}`);
54
+ }
55
+ }
45
56
  this.logDebug(debug, logger, `[DEBUG] Compressing files from path: ${flowFile}`);
46
57
  const plaintextZip = await compressFilesFromRelativePath(flowFile?.endsWith('.yaml') || flowFile?.endsWith('.yml')
47
58
  ? path.dirname(flowFile)
@@ -50,6 +61,7 @@ export class TestSubmissionService {
50
61
  ...referencedFiles,
51
62
  ...testFileNames,
52
63
  ...sequentialFlows,
64
+ ...includedFiles,
53
65
  ]),
54
66
  ], commonRoot);
55
67
  this.logDebug(debug, logger, `[DEBUG] Compressed file size: ${plaintextZip.length} bytes`);
@@ -12,6 +12,7 @@ export declare const WorkspaceConfigSchema: z.ZodObject<{
12
12
  flowsOrder: z.ZodArray<z.ZodString>;
13
13
  }, z.core.$strip>>>;
14
14
  flows: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
15
+ includedPaths: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
15
16
  includeTags: z.ZodOptional<z.ZodNullable<z.ZodPreprocess<z.ZodArray<z.ZodString>, unknown>>>;
16
17
  local: z.ZodOptional<z.ZodNullable<z.ZodObject<{
17
18
  deterministicOrder: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
@@ -42,6 +42,7 @@ export const WorkspaceConfigSchema = z.looseObject({
42
42
  excludeTags: tagList.nullish(),
43
43
  executionOrder: ExecutionOrderSchema.nullish(),
44
44
  flows: z.array(z.string()).nullish(),
45
+ includedPaths: z.array(z.string()).nullish(),
45
46
  includeTags: tagList.nullish(),
46
47
  local: z
47
48
  .looseObject({ deterministicOrder: z.boolean().nullish() })
@@ -80,10 +81,15 @@ export const WORKSPACE_CONFIG_KEYS = new Set(Object.keys(WorkspaceConfigSchema.s
80
81
  * Near-misses that aren't just a casing slip on a real key. Keyed lowercase.
81
82
  */
82
83
  const KEY_ALIASES = {
84
+ assets: 'includedPaths',
83
85
  continueonfailure: 'executionOrder.continueOnFailure',
84
86
  excludetag: 'excludeTags',
87
+ files: 'includedPaths',
85
88
  floworder: 'executionOrder.flowsOrder',
86
89
  flowsorder: 'executionOrder.flowsOrder',
90
+ includedpath: 'includedPaths',
91
+ includefiles: 'includedPaths',
92
+ includepaths: 'includedPaths',
87
93
  includetag: 'includeTags',
88
94
  tags: 'includeTags / excludeTags',
89
95
  };
package/package.json CHANGED
@@ -57,9 +57,19 @@
57
57
  },
58
58
  "repository": {
59
59
  "type": "git",
60
- "url": "https://devicecloud.dev"
60
+ "url": "git+https://github.com/devicecloud-dev/dcd-cli.git"
61
61
  },
62
- "version": "5.5.0-beta.5",
62
+ "scripts": {
63
+ "dcd": "tsx src/index.ts",
64
+ "build": "shx rm -rf dist && tsc -b && shx chmod +x dist/index.js dist/mcp/index.js",
65
+ "build:binaries": "node scripts/build-binaries.mjs",
66
+ "lint": "eslint src test --ext .ts",
67
+ "prepare": "pnpm build && husky",
68
+ "test": "node scripts/test-runner.mjs",
69
+ "test:unit": "node scripts/test-runner.mjs --unit",
70
+ "typecheck": "tsc --noEmit -p tsconfig.test.json"
71
+ },
72
+ "version": "5.6.0-beta.3",
63
73
  "bugs": {
64
74
  "url": "https://discord.gg/gm3mJwcNw8"
65
75
  },
@@ -72,13 +82,43 @@
72
82
  "testing"
73
83
  ],
74
84
  "types": "dist/index.d.ts",
75
- "scripts": {
76
- "dcd": "tsx src/index.ts",
77
- "build": "shx rm -rf dist && tsc -b && shx chmod +x dist/index.js dist/mcp/index.js",
78
- "build:binaries": "node scripts/build-binaries.mjs",
79
- "lint": "eslint src test --ext .ts",
80
- "test": "node scripts/test-runner.mjs",
81
- "test:unit": "node scripts/test-runner.mjs --unit",
82
- "typecheck": "tsc --noEmit -p tsconfig.test.json"
85
+ "packageManager": "pnpm@10.17.1",
86
+ "pnpm": {
87
+ "overrides": {
88
+ "js-yaml@<3.14.2": ">=3.14.2",
89
+ "js-yaml@>=4.0.0 <4.2.0": ">=4.2.0",
90
+ "js-yaml@>=5.0.0 <5.2.2": ">=5.2.2",
91
+ "tar@<7.5.16": ">=7.5.16",
92
+ "@isaacs/brace-expansion": ">=5.0.1",
93
+ "fast-xml-parser": ">=5.5.7",
94
+ "flatted": ">=3.4.2",
95
+ "lodash": ">=4.18.0",
96
+ "@xmldom/xmldom": ">=0.9.12",
97
+ "minimatch@<3.1.4": "3.1.4",
98
+ "minimatch@>=5.0.0 <5.1.8": "5.1.8",
99
+ "minimatch@>=9.0.0 <9.0.7": "9.0.7",
100
+ "minimatch@>=10.0.0 <10.2.3": "10.2.3",
101
+ "picomatch@<2.3.2": "2.3.2",
102
+ "picomatch@>=4.0.0 <4.0.4": "4.0.4",
103
+ "serialize-javascript": ">=7.0.5",
104
+ "ajv@<7.0.0": "6.14.0",
105
+ "diff@>=4.0.0 <6.0.0": "4.0.4",
106
+ "diff@>=6.0.0": "8.0.3",
107
+ "brace-expansion@<1.1.16": "1.1.16",
108
+ "brace-expansion@>=2.0.0 <2.0.3": "2.0.3",
109
+ "brace-expansion@>=3.0.0 <5.0.9": "5.0.9",
110
+ "ws@>=8.0.0 <8.21.0": "8.21.0",
111
+ "esbuild@<0.28.1": ">=0.28.1",
112
+ "qs@<6.16.0": "6.16.0",
113
+ "fast-uri@>=3.0.0 <3.1.6": "3.1.6",
114
+ "hono@>=4.0.0 <4.13.5": "4.13.5",
115
+ "micromatch>picomatch": "^2.3.2",
116
+ "tinyglobby>picomatch": "^4.0.4"
117
+ },
118
+ "auditConfig": {
119
+ "ignoreGhsas": [
120
+ "GHSA-frvp-7c67-39w9"
121
+ ]
122
+ }
83
123
  }
84
- }
124
+ }