@dereekb/dbx-cli 13.43.0 → 14.0.1

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/validate/index.js CHANGED
@@ -2909,6 +2909,96 @@ var RULE_CATALOG = [
2909
2909
  whenItApplies: "When a key on `<app>SystemStateStoredDataConverterMap` is neither a local `<NAME>_SYSTEM_STATE_TYPE` constant nor an imported identifier.",
2910
2910
  whenItDoesNotApply: "Keys imported from upstream packages \u2014 those are detected via `importedIdentifiers` and skipped.",
2911
2911
  canonicalFix: "Either declare the missing constant locally or import it from the upstream package that owns it."
2912
+ },
2913
+ {
2914
+ code: "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_MISSING",
2915
+ source: "dbx_workspace_validate",
2916
+ severity: "error",
2917
+ title: "A configuration's file-replacement or esbuild-config path does not exist on disk",
2918
+ whatItFlags: "A configuration's file-replacement or esbuild-config path does not exist on disk.",
2919
+ whenItApplies: "When a `fileReplacements` `replace` / `with` path, or a configuration's `esbuildConfig` path, names a file that is absent from the working tree.",
2920
+ whenItDoesNotApply: "Generated files produced by an earlier target in the same pipeline \u2014 declare them as that target's `outputs` so the dependency is explicit, or the reference is genuinely broken.",
2921
+ canonicalFix: "Create the missing file or correct the path. An Angular build fails loudly on a missing replacement, but an esbuild `additionalProperties: true` schema accepts a bad path silently."
2922
+ },
2923
+ {
2924
+ code: "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_UNREFERENCED",
2925
+ source: "dbx_workspace_validate",
2926
+ severity: "warning",
2927
+ title: "An `environment.<name>.ts` file exists but no build configuration references it",
2928
+ whatItFlags: "An `environment.<name>.ts` file exists but no build configuration references it.",
2929
+ whenItApplies: "When a file matching `environment.*.ts` sits in a project's `src/environments/` directory and appears in no configuration's `fileReplacements`, and no `esbuildConfig` file references it.",
2930
+ whenItDoesNotApply: "The base `environment.ts` (always the replacement source, never a target), and environment files imported directly by application code rather than swapped in by the build.",
2931
+ canonicalFix: "Add the configuration that consumes it, or delete the file. An unreachable environment file reads as a working deploy lane that does not exist \u2014 the file is edited, reviewed, and never compiled."
2932
+ },
2933
+ {
2934
+ code: "WORKSPACE_DEPLOY_HOSTING_TARGET_MISSING",
2935
+ source: "dbx_workspace_validate",
2936
+ severity: "error",
2937
+ title: "A deploy command targets a firebase hosting target that `firebase.json` does not declare",
2938
+ whatItFlags: "A deploy command targets a firebase hosting target that `firebase.json` does not declare.",
2939
+ whenItApplies: "When a deploy command runs `firebase deploy --only hosting:<target>` and `<target>` matches no `target` (or `site`) entry under `hosting` in `firebase.json`.",
2940
+ whenItDoesNotApply: "Workspaces that resolve hosting targets through `.firebaserc` target aliases the scanner does not read \u2014 verify manually if this misfires.",
2941
+ canonicalFix: "Add the hosting entry to `firebase.json`, or point the command at a declared target. `firebase deploy` fails on an unknown hosting target, so this breaks the lane outright."
2942
+ },
2943
+ {
2944
+ code: "WORKSPACE_DEPLOY_LANE_ASYMMETRIC",
2945
+ source: "dbx_workspace_validate",
2946
+ severity: "warning",
2947
+ title: "A deploy lane is wired on one side of the app/api pair but not the other",
2948
+ whatItFlags: "A deploy lane is wired on one side of the app/api pair but not the other.",
2949
+ whenItApplies: "When one project in the workspace declares `ci-deploy-<lane>` and a sibling project that declares at least one other `ci-deploy-*` lane does not declare this one.",
2950
+ whenItDoesNotApply: "Projects deliberately deployed on their own cadence, and workspaces with a single deployable project. Suppress by giving the sibling the lane, or accept the warning.",
2951
+ canonicalFix: "Add the missing `ci-deploy-<lane>` target to the sibling project. A lane present on the API but not the web app (or vice versa) deploys half an application and leaves the two halves on different builds."
2952
+ },
2953
+ {
2954
+ code: "WORKSPACE_DEPLOY_LANE_NO_ENVIRONMENT_SELECTION",
2955
+ source: "dbx_workspace_validate",
2956
+ severity: "error",
2957
+ title: "A deploy lane builds a configuration that selects no environment file",
2958
+ whatItFlags: "A deploy lane builds a configuration that selects no environment file.",
2959
+ whenItApplies: "When a `ci-deploy-<lane>` target builds a configuration that provides no environment-selection mechanism for its executor \u2014 no `fileReplacements` for an Angular `application` build, or no lane-specific `esbuildConfig` for an `@nx/esbuild:esbuild` build.",
2960
+ whenItDoesNotApply: "Non-deploy configurations such as `development`, which are expected to compile the base `environment.ts`. Only configurations reachable from a `ci-deploy-*` target are checked.",
2961
+ canonicalFix: "Add the `fileReplacements` entry (or the lane's `esbuildConfig`) to the configuration. Without it the lane deploys the unreplaced `environment.ts` \u2014 localhost URLs, dev credentials, and any developer-only function map \u2014 to a live project."
2962
+ },
2963
+ {
2964
+ code: "WORKSPACE_TARGET_OUTPUT_ESCAPES_ROOT",
2965
+ source: "dbx_workspace_validate",
2966
+ severity: "error",
2967
+ title: "A target's `outputs` entry resolves outside the workspace root",
2968
+ whatItFlags: "A target's `outputs` entry resolves outside the workspace root.",
2969
+ whenItApplies: 'When an `outputs` entry \u2014 after `{options.<name>}` substitution from the target\'s own options \u2014 resolves to a path above the workspace root. The common shape is `outputs: ["{options.reportsDirectory}"]` with a `../../`-relative option value.',
2970
+ whenItDoesNotApply: "Entries anchored by `{workspaceRoot}` or `{projectRoot}`, and entries whose `{options.<name>}` token has no resolvable value (skipped rather than guessed).",
2971
+ canonicalFix: 'Anchor the entry explicitly, e.g. `"{workspaceRoot}/coverage/{projectRoot}"`. Nx refuses to cache an output it cannot place inside the workspace and will report the task as flaky instead of failing loudly.'
2972
+ },
2973
+ {
2974
+ code: "WORKSPACE_TARGET_REF_CONFIGURATION_MISSING",
2975
+ source: "dbx_workspace_validate",
2976
+ severity: "error",
2977
+ title: "An `nx run <project>:<target>:<configuration>` reference names a configuration the target does not declare",
2978
+ whatItFlags: "An `nx run <project>:<target>:<configuration>` reference names a configuration the target does not declare.",
2979
+ whenItApplies: "Whenever a reference carries a configuration segment and the target's `configurations` map has no such key.",
2980
+ whenItDoesNotApply: "Targets that declare no `configurations` at all \u2014 Nx tolerates a configuration argument there, so it is not reported.",
2981
+ canonicalFix: "Rename the reference to a declared configuration (a `prod` / `production` mismatch is the usual cause), or add the configuration to the target. Nx does NOT error on an unknown configuration: `create-task-graph.js` silently substitutes `defaultConfiguration`, so a mismatch here ships whatever that default builds \u2014 and ships the base options with no configuration merged at all when no default is set."
2982
+ },
2983
+ {
2984
+ code: "WORKSPACE_TARGET_REF_PROJECT_MISSING",
2985
+ source: "dbx_workspace_validate",
2986
+ severity: "error",
2987
+ title: "An `nx run <project>:...` reference names a project that does not exist in the workspace",
2988
+ whatItFlags: "An `nx run <project>:...` reference names a project that does not exist in the workspace.",
2989
+ whenItApplies: "When a target reference in a `project.json` command, CI config, shell script, or npm script names a project with no matching entry in the workspace project graph.",
2990
+ whenItDoesNotApply: "References built from shell variables or CI interpolation the scanner cannot resolve \u2014 those are skipped rather than reported.",
2991
+ canonicalFix: "Create the project, or update the reference to the project's real name. A renamed or deleted project leaves these references behind and they only fail at deploy time."
2992
+ },
2993
+ {
2994
+ code: "WORKSPACE_TARGET_REF_TARGET_MISSING",
2995
+ source: "dbx_workspace_validate",
2996
+ severity: "error",
2997
+ title: "An `nx run <project>:<target>` reference names a target the project does not declare",
2998
+ whatItFlags: "An `nx run <project>:<target>` reference names a target the project does not declare.",
2999
+ whenItApplies: "When the referenced project exists but has no target by that name (after `nx.json` `targetDefaults` are accounted for).",
3000
+ whenItDoesNotApply: "Targets contributed by an inferred-target plugin the scanner does not evaluate \u2014 declare the plugin's targets explicitly if this misfires.",
3001
+ canonicalFix: "Add the target to the project, or point the reference at an existing one. This is the single most common way a CI lane silently stops doing anything."
2912
3002
  }
2913
3003
  ];
2914
3004
 
@@ -2970,7 +3060,31 @@ function attachRemediation(code3) {
2970
3060
 
2971
3061
  // packages/dbx-cli/validate/src/lib/_core/rule-catalog/format.ts
2972
3062
  function formatRuleAsMarkdown(entry) {
2973
- const lines = [`# \`${entry.code}\``, "", `**${entry.title}**`, "", `- **Severity:** \`${entry.severity}\``, `- **Source tool:** \`${entry.source}\``, "", "## What it flags", "", entry.whatItFlags, "", "## When it applies", "", entry.whenItApplies, "", "## When it does NOT apply", "", entry.whenItDoesNotApply, "", "## Canonical fix", "", entry.canonicalFix, ""];
3063
+ const lines = [
3064
+ `# \`${entry.code}\``,
3065
+ "",
3066
+ `**${entry.title}**`,
3067
+ "",
3068
+ `- **Severity:** \`${entry.severity}\``,
3069
+ `- **Source tool:** \`${entry.source}\``,
3070
+ "",
3071
+ "## What it flags",
3072
+ "",
3073
+ entry.whatItFlags,
3074
+ "",
3075
+ "## When it applies",
3076
+ "",
3077
+ entry.whenItApplies,
3078
+ "",
3079
+ "## When it does NOT apply",
3080
+ "",
3081
+ entry.whenItDoesNotApply,
3082
+ "",
3083
+ "## Canonical fix",
3084
+ "",
3085
+ entry.canonicalFix,
3086
+ ""
3087
+ ];
2974
3088
  if (entry.canonicalFixTemplate) {
2975
3089
  lines.push("## Fix template", "", entry.canonicalFixTemplate, "");
2976
3090
  }
@@ -2996,22 +3110,22 @@ function formatSeeAlso(ref) {
2996
3110
  // packages/dbx-cli/validate/src/lib/_core/validate-input.ts
2997
3111
  import { glob as fsGlob, readFile, stat } from "node:fs/promises";
2998
3112
  import { resolve, sep } from "node:path";
2999
- function ensurePathInsideCwd(relative12, cwd) {
3113
+ function ensurePathInsideCwd(relative13, cwd) {
3000
3114
  const cwdPrefix = cwd.endsWith(sep) ? cwd : cwd + sep;
3001
- const absolute = resolve(cwd, relative12);
3115
+ const absolute = resolve(cwd, relative13);
3002
3116
  if (!absolute.startsWith(cwdPrefix) && absolute !== cwd) {
3003
- throw new Error(`Path \`${relative12}\` resolves outside the server cwd and is not allowed.`);
3117
+ throw new Error(`Path \`${relative13}\` resolves outside the server cwd and is not allowed.`);
3004
3118
  }
3005
3119
  }
3006
3120
  async function resolveFolderPaths(config) {
3007
3121
  const { paths, glob, cwd } = config;
3008
3122
  const collected = [];
3009
3123
  const seen = /* @__PURE__ */ new Set();
3010
- const accept = (relative12) => {
3011
- if (seen.has(relative12)) return;
3012
- ensurePathInsideCwd(relative12, cwd);
3013
- seen.add(relative12);
3014
- collected.push(relative12);
3124
+ const accept = (relative13) => {
3125
+ if (seen.has(relative13)) return;
3126
+ ensurePathInsideCwd(relative13, cwd);
3127
+ seen.add(relative13);
3128
+ collected.push(relative13);
3015
3129
  };
3016
3130
  if (paths) {
3017
3131
  for (const p of paths) {
@@ -3054,13 +3168,13 @@ async function resolveValidatorSources(config) {
3054
3168
  pathList.push(match);
3055
3169
  }
3056
3170
  }
3057
- for (const relative12 of pathList) {
3058
- if (seenNames.has(relative12)) continue;
3059
- ensurePathInsideCwd(relative12, cwd);
3060
- const absolute = resolve(cwd, relative12);
3171
+ for (const relative13 of pathList) {
3172
+ if (seenNames.has(relative13)) continue;
3173
+ ensurePathInsideCwd(relative13, cwd);
3174
+ const absolute = resolve(cwd, relative13);
3061
3175
  const text = await readFile(absolute, "utf8");
3062
- seenNames.add(relative12);
3063
- collected.push({ name: relative12, text });
3176
+ seenNames.add(relative13);
3177
+ collected.push({ name: relative13, text });
3064
3178
  }
3065
3179
  return collected;
3066
3180
  }
@@ -3115,12 +3229,12 @@ function formatRemediationBlock(hint) {
3115
3229
  return lines.join("\n");
3116
3230
  }
3117
3231
  function aggregateFolderRules(config) {
3118
- const { inspections, runRules: runRules9 } = config;
3232
+ const { inspections, runRules: runRules10 } = config;
3119
3233
  const violations = [];
3120
3234
  let errorCount = 0;
3121
3235
  let warningCount = 0;
3122
3236
  for (const inspection of inspections) {
3123
- const folderViolations = runRules9(inspection);
3237
+ const folderViolations = runRules10(inspection);
3124
3238
  for (const v of folderViolations) {
3125
3239
  violations.push(v);
3126
3240
  if (v.severity === "error") {
@@ -12739,6 +12853,939 @@ function appendDiagnosticEntry(lines, d) {
12739
12853
  }
12740
12854
  }
12741
12855
 
12856
+ // packages/dbx-cli/validate/src/lib/workspace-validate/index.ts
12857
+ var workspace_validate_exports = {};
12858
+ __export(workspace_validate_exports, {
12859
+ DEPLOY_TARGET_PREFIX: () => DEPLOY_TARGET_PREFIX,
12860
+ WORKSPACE_RULE_GROUPS: () => WORKSPACE_RULE_GROUPS,
12861
+ WorkspaceValidateCode: () => WorkspaceValidateCode,
12862
+ formatResult: () => formatResult11,
12863
+ inspectWorkspace: () => inspectWorkspace,
12864
+ pathEscapesWorkspaceRoot: () => pathEscapesWorkspaceRoot,
12865
+ scanDocumentTargetReferences: () => scanDocumentTargetReferences,
12866
+ scanHostingTargets: () => scanHostingTargets,
12867
+ scanTargetReferences: () => scanTargetReferences,
12868
+ validateWorkspace: () => validateWorkspace
12869
+ });
12870
+
12871
+ // packages/dbx-cli/validate/src/lib/workspace-validate/codes.ts
12872
+ var WorkspaceValidateCode = /* @__PURE__ */ ((WorkspaceValidateCode2) => {
12873
+ WorkspaceValidateCode2["WORKSPACE_TARGET_REF_PROJECT_MISSING"] = "WORKSPACE_TARGET_REF_PROJECT_MISSING";
12874
+ WorkspaceValidateCode2["WORKSPACE_TARGET_REF_TARGET_MISSING"] = "WORKSPACE_TARGET_REF_TARGET_MISSING";
12875
+ WorkspaceValidateCode2["WORKSPACE_TARGET_REF_CONFIGURATION_MISSING"] = "WORKSPACE_TARGET_REF_CONFIGURATION_MISSING";
12876
+ WorkspaceValidateCode2["WORKSPACE_TARGET_OUTPUT_ESCAPES_ROOT"] = "WORKSPACE_TARGET_OUTPUT_ESCAPES_ROOT";
12877
+ WorkspaceValidateCode2["WORKSPACE_DEPLOY_LANE_NO_ENVIRONMENT_SELECTION"] = "WORKSPACE_DEPLOY_LANE_NO_ENVIRONMENT_SELECTION";
12878
+ WorkspaceValidateCode2["WORKSPACE_DEPLOY_ENVIRONMENT_FILE_MISSING"] = "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_MISSING";
12879
+ WorkspaceValidateCode2["WORKSPACE_DEPLOY_ENVIRONMENT_FILE_UNREFERENCED"] = "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_UNREFERENCED";
12880
+ WorkspaceValidateCode2["WORKSPACE_DEPLOY_HOSTING_TARGET_MISSING"] = "WORKSPACE_DEPLOY_HOSTING_TARGET_MISSING";
12881
+ WorkspaceValidateCode2["WORKSPACE_DEPLOY_LANE_ASYMMETRIC"] = "WORKSPACE_DEPLOY_LANE_ASYMMETRIC";
12882
+ return WorkspaceValidateCode2;
12883
+ })(WorkspaceValidateCode || {});
12884
+
12885
+ // packages/dbx-cli/validate/src/lib/workspace-validate/format.ts
12886
+ function formatResult11(result) {
12887
+ return formatFileGroupedResult({
12888
+ title: "Workspace convention validation",
12889
+ summary: `Checked ${result.projectsChecked} project(s) and ${result.referencesChecked} target reference(s) across group(s) [${result.groups.join(", ")}].`,
12890
+ innerKey: (violation) => violation.project,
12891
+ result
12892
+ });
12893
+ }
12894
+
12895
+ // packages/dbx-cli/validate/src/lib/workspace-validate/inspect.ts
12896
+ import { readdir as readdir8, readFile as readFile11, stat as stat10 } from "node:fs/promises";
12897
+ import { basename as basename5, dirname as dirname2, join as join10, relative as relative6, resolve as resolve3 } from "node:path";
12898
+
12899
+ // packages/dbx-cli/validate/src/lib/workspace-validate/references.ts
12900
+ var NX_SUBCOMMANDS = /* @__PURE__ */ new Set([
12901
+ "run",
12902
+ "run-many",
12903
+ "affected",
12904
+ "affected:graph",
12905
+ "affected:test",
12906
+ "affected:build",
12907
+ "graph",
12908
+ "dep-graph",
12909
+ "print-affected",
12910
+ "show",
12911
+ "list",
12912
+ "report",
12913
+ "migrate",
12914
+ "reset",
12915
+ "repair",
12916
+ "generate",
12917
+ "g",
12918
+ "add",
12919
+ "init",
12920
+ "import",
12921
+ "sync",
12922
+ "release",
12923
+ "format",
12924
+ "format:check",
12925
+ "format:write",
12926
+ "daemon",
12927
+ "connect",
12928
+ "login",
12929
+ "logout",
12930
+ "view-logs",
12931
+ "exec",
12932
+ "watch",
12933
+ "documentation",
12934
+ "help",
12935
+ "register",
12936
+ "record",
12937
+ "start-ci-run"
12938
+ ]);
12939
+ var INTERPOLATION_PATTERN = /[$`{}()*?]|\\\\/;
12940
+ var TOKEN_LEADING_CHARS = `"'`;
12941
+ var TOKEN_TRAILING_CHARS = `"',;`;
12942
+ var PROJECT_TOKEN_PATTERN = /^[a-zA-Z0-9@][a-zA-Z0-9@/_.-]*$/;
12943
+ var TARGET_TOKEN_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/;
12944
+ function isFlag(token) {
12945
+ return token.startsWith("-");
12946
+ }
12947
+ function isResolvable(token) {
12948
+ return token.length > 0 && !INTERPOLATION_PATTERN.test(token);
12949
+ }
12950
+ function trimTokenPunctuation(raw) {
12951
+ let start = 0;
12952
+ let end = raw.length;
12953
+ while (start < end && TOKEN_LEADING_CHARS.includes(raw[start])) {
12954
+ start += 1;
12955
+ }
12956
+ while (end > start && TOKEN_TRAILING_CHARS.includes(raw[end - 1])) {
12957
+ end -= 1;
12958
+ }
12959
+ return raw.slice(start, end);
12960
+ }
12961
+ function tokenize(text) {
12962
+ const out = [];
12963
+ for (const raw of text.split(/\s+/)) {
12964
+ const token = trimTokenPunctuation(raw);
12965
+ if (token.length > 0) {
12966
+ out.push(token);
12967
+ }
12968
+ }
12969
+ return out;
12970
+ }
12971
+ function isNxBinary(token) {
12972
+ return token === "nx" || token.endsWith("/nx");
12973
+ }
12974
+ function changesToForeignDirectory(tokens) {
12975
+ let result = false;
12976
+ for (let i = 0; i < tokens.length - 1 && !result; i += 1) {
12977
+ if (tokens[i] === "cd") {
12978
+ const destination = tokens[i + 1];
12979
+ result = destination.startsWith("~") || destination.startsWith("/");
12980
+ }
12981
+ }
12982
+ return result;
12983
+ }
12984
+ function parseExplicitForm(tokens, runIndex) {
12985
+ let result;
12986
+ for (let i = runIndex + 1; i < tokens.length; i += 1) {
12987
+ const token = tokens[i];
12988
+ if (isFlag(token)) {
12989
+ continue;
12990
+ }
12991
+ if (isResolvable(token) && token.includes(":")) {
12992
+ const [project, target, configuration] = token.split(":");
12993
+ if (project && target && PROJECT_TOKEN_PATTERN.test(project) && TARGET_TOKEN_PATTERN.test(target)) {
12994
+ result = { project, target, configuration: configuration || void 0, raw: token };
12995
+ }
12996
+ }
12997
+ break;
12998
+ }
12999
+ return result;
13000
+ }
13001
+ function parseShorthandForm(tokens, nxIndex) {
13002
+ const positional = [];
13003
+ for (let i = nxIndex + 1; i < tokens.length && positional.length < 2; i += 1) {
13004
+ const token = tokens[i];
13005
+ if (!isFlag(token)) {
13006
+ positional.push(token);
13007
+ }
13008
+ }
13009
+ let result;
13010
+ const [target, project] = positional;
13011
+ if (target !== void 0 && project !== void 0 && !NX_SUBCOMMANDS.has(target) && !target.includes(":") && isResolvable(target) && isResolvable(project) && TARGET_TOKEN_PATTERN.test(target) && PROJECT_TOKEN_PATTERN.test(project)) {
13012
+ result = { project, target, raw: `${target} ${project}` };
13013
+ }
13014
+ return result;
13015
+ }
13016
+ function scanTargetReferences(input) {
13017
+ const { text, sourceFile, sourceTarget, line } = input;
13018
+ const references = [];
13019
+ const tokens = tokenize(text);
13020
+ if (changesToForeignDirectory(tokens)) {
13021
+ return references;
13022
+ }
13023
+ for (let i = 0; i < tokens.length; i += 1) {
13024
+ if (!isNxBinary(tokens[i])) {
13025
+ continue;
13026
+ }
13027
+ let firstPositional;
13028
+ let firstPositionalIndex = -1;
13029
+ for (let j = i + 1; j < tokens.length; j += 1) {
13030
+ if (!isFlag(tokens[j])) {
13031
+ firstPositional = tokens[j];
13032
+ firstPositionalIndex = j;
13033
+ break;
13034
+ }
13035
+ }
13036
+ if (firstPositional === void 0) {
13037
+ continue;
13038
+ }
13039
+ if (firstPositional === "run") {
13040
+ const parsed = parseExplicitForm(tokens, firstPositionalIndex);
13041
+ if (parsed) {
13042
+ references.push({ project: parsed.project, target: parsed.target, configuration: parsed.configuration, sourceFile, line, sourceTarget, raw: parsed.raw });
13043
+ }
13044
+ } else if (!NX_SUBCOMMANDS.has(firstPositional)) {
13045
+ const parsed = parseShorthandForm(tokens, i);
13046
+ if (parsed) {
13047
+ references.push({ project: parsed.project, target: parsed.target, configuration: void 0, sourceFile, line, sourceTarget, raw: parsed.raw });
13048
+ }
13049
+ }
13050
+ }
13051
+ return references;
13052
+ }
13053
+ function stripComment(line) {
13054
+ const trimmed = line.trimStart();
13055
+ let result;
13056
+ if (trimmed.startsWith("#")) {
13057
+ result = "";
13058
+ } else {
13059
+ const index = line.indexOf(" #");
13060
+ result = index === -1 ? line : line.slice(0, index);
13061
+ }
13062
+ return result;
13063
+ }
13064
+ function scanDocumentTargetReferences(input) {
13065
+ const { text, sourceFile } = input;
13066
+ const references = [];
13067
+ const lines = text.split("\n");
13068
+ for (const [index, line] of lines.entries()) {
13069
+ for (const reference of scanTargetReferences({ text: stripComment(line), sourceFile, line: index + 1 })) {
13070
+ references.push(reference);
13071
+ }
13072
+ }
13073
+ return references;
13074
+ }
13075
+ function scanHostingTargets(command) {
13076
+ const out = [];
13077
+ const pattern = /hosting:([a-zA-Z0-9_-]+)/g;
13078
+ let match = pattern.exec(command);
13079
+ while (match !== null) {
13080
+ out.push(match[1]);
13081
+ match = pattern.exec(command);
13082
+ }
13083
+ return out;
13084
+ }
13085
+
13086
+ // packages/dbx-cli/validate/src/lib/workspace-validate/inspect.ts
13087
+ var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", "dist", "tmp", "coverage", ".nx", ".git", ".firebase", ".angular", ".cache", "templates"]);
13088
+ var MAX_WALK_DEPTH = 4;
13089
+ var PROJECT_CONFIG_FILENAME = "project.json";
13090
+ var ENVIRONMENTS_SUBPATH = join10("src", "environments");
13091
+ var ENVIRONMENT_FILE_PATTERN = /^environment\..+\.ts$/;
13092
+ var EXTRA_REFERENCE_SOURCES = [".circleci/config.yml", ".circleci/config.yaml", ".github/workflows"];
13093
+ async function pathExists(path) {
13094
+ let result;
13095
+ try {
13096
+ await stat10(path);
13097
+ result = true;
13098
+ } catch {
13099
+ result = false;
13100
+ }
13101
+ return result;
13102
+ }
13103
+ async function readJsonFile(path) {
13104
+ let result;
13105
+ try {
13106
+ const text = await readFile11(path, "utf8");
13107
+ const parsed = JSON.parse(text);
13108
+ result = parsed !== null && typeof parsed === "object" ? parsed : void 0;
13109
+ } catch {
13110
+ result = void 0;
13111
+ }
13112
+ return result;
13113
+ }
13114
+ async function collectProjectConfigPaths(config) {
13115
+ const { workspaceRoot, relativeDir, depth } = config;
13116
+ const collected = [];
13117
+ const absoluteDir = join10(workspaceRoot, relativeDir);
13118
+ const entries = await readdir8(absoluteDir, { withFileTypes: true }).catch(() => []);
13119
+ for (const entry of entries) {
13120
+ if (entry.isFile() && entry.name === PROJECT_CONFIG_FILENAME) {
13121
+ collected.push(relativeDir === "" ? PROJECT_CONFIG_FILENAME : join10(relativeDir, PROJECT_CONFIG_FILENAME));
13122
+ } else if (entry.isDirectory() && depth < MAX_WALK_DEPTH && !SKIPPED_DIRECTORIES.has(entry.name) && !entry.name.startsWith("firebase-export-")) {
13123
+ const nested = await collectProjectConfigPaths({ workspaceRoot, relativeDir: relativeDir === "" ? entry.name : join10(relativeDir, entry.name), depth: depth + 1 });
13124
+ for (const path of nested) {
13125
+ collected.push(path);
13126
+ }
13127
+ }
13128
+ }
13129
+ return collected;
13130
+ }
13131
+ function extractCommands(options) {
13132
+ const collected = [];
13133
+ const single = options["command"];
13134
+ if (typeof single === "string") {
13135
+ collected.push(single);
13136
+ }
13137
+ const many = options["commands"];
13138
+ if (Array.isArray(many)) {
13139
+ for (const entry of many) {
13140
+ if (typeof entry === "string") {
13141
+ collected.push(entry);
13142
+ } else if (entry !== null && typeof entry === "object") {
13143
+ const command = entry["command"];
13144
+ if (typeof command === "string") {
13145
+ collected.push(command);
13146
+ }
13147
+ }
13148
+ }
13149
+ }
13150
+ return collected;
13151
+ }
13152
+ async function resolveFileReplacements(config) {
13153
+ const { workspaceRoot, raw } = config;
13154
+ const collected = [];
13155
+ if (Array.isArray(raw)) {
13156
+ for (const entry of raw) {
13157
+ if (entry === null || typeof entry !== "object") {
13158
+ continue;
13159
+ }
13160
+ const record = entry;
13161
+ const replace = record["replace"];
13162
+ const withPath = record["with"];
13163
+ if (typeof replace === "string" && typeof withPath === "string") {
13164
+ collected.push({
13165
+ replace,
13166
+ with: withPath,
13167
+ replaceExists: await pathExists(join10(workspaceRoot, replace)),
13168
+ withExists: await pathExists(join10(workspaceRoot, withPath))
13169
+ });
13170
+ }
13171
+ }
13172
+ }
13173
+ return collected;
13174
+ }
13175
+ async function resolveConfigurations(config) {
13176
+ const { workspaceRoot, raw } = config;
13177
+ const collected = [];
13178
+ if (raw !== null && typeof raw === "object") {
13179
+ for (const [name, value] of Object.entries(raw)) {
13180
+ const options = value !== null && typeof value === "object" ? value : {};
13181
+ const esbuildConfigValue = options["esbuildConfig"];
13182
+ const esbuildConfig = typeof esbuildConfigValue === "string" ? esbuildConfigValue : void 0;
13183
+ collected.push({
13184
+ name,
13185
+ fileReplacements: await resolveFileReplacements({ workspaceRoot, raw: options["fileReplacements"] }),
13186
+ esbuildConfig,
13187
+ esbuildConfigExists: esbuildConfig === void 0 ? false : await pathExists(join10(workspaceRoot, esbuildConfig))
13188
+ });
13189
+ }
13190
+ }
13191
+ return collected;
13192
+ }
13193
+ async function resolveTargets(config) {
13194
+ const { workspaceRoot, raw } = config;
13195
+ const collected = [];
13196
+ if (raw !== null && typeof raw === "object") {
13197
+ for (const [name, value] of Object.entries(raw)) {
13198
+ const target = value !== null && typeof value === "object" ? value : {};
13199
+ const executorValue = target["executor"];
13200
+ const defaultConfigurationValue = target["defaultConfiguration"];
13201
+ const optionsValue = target["options"];
13202
+ const options = optionsValue !== null && typeof optionsValue === "object" ? optionsValue : {};
13203
+ const outputsValue = target["outputs"];
13204
+ collected.push({
13205
+ name,
13206
+ executor: typeof executorValue === "string" ? executorValue : void 0,
13207
+ configurations: await resolveConfigurations({ workspaceRoot, raw: target["configurations"] }),
13208
+ defaultConfiguration: typeof defaultConfigurationValue === "string" ? defaultConfigurationValue : void 0,
13209
+ outputs: Array.isArray(outputsValue) ? outputsValue.filter((o) => typeof o === "string") : [],
13210
+ options,
13211
+ commands: extractCommands(options)
13212
+ });
13213
+ }
13214
+ }
13215
+ return collected;
13216
+ }
13217
+ async function collectEnvironmentFiles(config) {
13218
+ const { workspaceRoot, projectRoot } = config;
13219
+ const relativeDir = projectRoot === "" ? ENVIRONMENTS_SUBPATH : join10(projectRoot, ENVIRONMENTS_SUBPATH);
13220
+ const collected = [];
13221
+ const entries = await readdir8(join10(workspaceRoot, relativeDir), { withFileTypes: true }).catch(() => []);
13222
+ for (const entry of entries) {
13223
+ if (entry.isFile() && ENVIRONMENT_FILE_PATTERN.test(entry.name)) {
13224
+ collected.push(join10(relativeDir, entry.name));
13225
+ }
13226
+ }
13227
+ return collected.sort((a, b) => a.localeCompare(b));
13228
+ }
13229
+ var ENVIRONMENT_MENTION_PATTERN = /environment(?:\.[A-Za-z0-9_-]+)*\.ts/g;
13230
+ async function collectReferencedEnvironmentFiles(config) {
13231
+ const { workspaceRoot, targets, environmentFiles } = config;
13232
+ const referenced = /* @__PURE__ */ new Set();
13233
+ const esbuildConfigPaths = /* @__PURE__ */ new Set();
13234
+ for (const target of targets) {
13235
+ const baseEsbuildConfig = target.options["esbuildConfig"];
13236
+ if (typeof baseEsbuildConfig === "string") {
13237
+ esbuildConfigPaths.add(baseEsbuildConfig);
13238
+ }
13239
+ for (const configuration of target.configurations) {
13240
+ for (const replacement of configuration.fileReplacements) {
13241
+ referenced.add(replacement.with);
13242
+ }
13243
+ if (configuration.esbuildConfig !== void 0) {
13244
+ esbuildConfigPaths.add(configuration.esbuildConfig);
13245
+ }
13246
+ }
13247
+ }
13248
+ const mentionedBasenames = /* @__PURE__ */ new Set();
13249
+ for (const configPath of esbuildConfigPaths) {
13250
+ const text = await readFile11(join10(workspaceRoot, configPath), "utf8").catch(() => void 0);
13251
+ if (text === void 0) {
13252
+ continue;
13253
+ }
13254
+ for (const match of text.matchAll(ENVIRONMENT_MENTION_PATTERN)) {
13255
+ mentionedBasenames.add(match[0]);
13256
+ }
13257
+ }
13258
+ for (const environmentFile of environmentFiles) {
13259
+ if (mentionedBasenames.has(basename5(environmentFile))) {
13260
+ referenced.add(environmentFile);
13261
+ }
13262
+ }
13263
+ return Array.from(referenced).sort((a, b) => a.localeCompare(b));
13264
+ }
13265
+ async function inspectProject(config) {
13266
+ const { workspaceRoot, configFile } = config;
13267
+ const parsed = await readJsonFile(join10(workspaceRoot, configFile));
13268
+ if (parsed === void 0) {
13269
+ return void 0;
13270
+ }
13271
+ const projectRoot = dirname2(configFile) === "." ? "" : dirname2(configFile);
13272
+ const nameValue = parsed["name"];
13273
+ const fallbackNameDir = projectRoot === "" ? workspaceRoot : projectRoot;
13274
+ const name = typeof nameValue === "string" && nameValue.length > 0 ? nameValue : basename5(fallbackNameDir);
13275
+ const targets = await resolveTargets({ workspaceRoot, raw: parsed["targets"] });
13276
+ const environmentFiles = await collectEnvironmentFiles({ workspaceRoot, projectRoot });
13277
+ return {
13278
+ name,
13279
+ root: projectRoot,
13280
+ configFile,
13281
+ targets,
13282
+ environmentFiles,
13283
+ referencedEnvironmentFiles: await collectReferencedEnvironmentFiles({ workspaceRoot, targets, environmentFiles })
13284
+ };
13285
+ }
13286
+ async function inspectNxConfig(workspaceRoot) {
13287
+ const parsed = await readJsonFile(join10(workspaceRoot, "nx.json"));
13288
+ const targetDefaultNames = [];
13289
+ const plugins = [];
13290
+ const targetDefaults = parsed?.["targetDefaults"];
13291
+ if (targetDefaults !== null && typeof targetDefaults === "object") {
13292
+ for (const key of Object.keys(targetDefaults)) {
13293
+ if (!key.includes(":")) {
13294
+ targetDefaultNames.push(key);
13295
+ }
13296
+ }
13297
+ }
13298
+ const pluginsValue = parsed?.["plugins"];
13299
+ if (Array.isArray(pluginsValue)) {
13300
+ for (const entry of pluginsValue) {
13301
+ if (typeof entry === "string") {
13302
+ plugins.push(entry);
13303
+ } else if (entry !== null && typeof entry === "object") {
13304
+ const plugin = entry["plugin"];
13305
+ if (typeof plugin === "string") {
13306
+ plugins.push(plugin);
13307
+ }
13308
+ }
13309
+ }
13310
+ }
13311
+ return { targetDefaultNames, plugins };
13312
+ }
13313
+ async function inspectFirebaseConfig(workspaceRoot) {
13314
+ const parsed = await readJsonFile(join10(workspaceRoot, "firebase.json"));
13315
+ if (parsed === void 0) {
13316
+ return { present: false, hostingTargets: [] };
13317
+ }
13318
+ const hostingValue = parsed["hosting"];
13319
+ const singleHostingEntry = hostingValue !== null && typeof hostingValue === "object" ? [hostingValue] : [];
13320
+ const entries = Array.isArray(hostingValue) ? hostingValue : singleHostingEntry;
13321
+ const hostingTargets = [];
13322
+ for (const entry of entries) {
13323
+ if (entry === null || typeof entry !== "object") {
13324
+ continue;
13325
+ }
13326
+ const record = entry;
13327
+ const target = record["target"];
13328
+ const site = record["site"];
13329
+ if (typeof target === "string") {
13330
+ hostingTargets.push(target);
13331
+ } else if (typeof site === "string") {
13332
+ hostingTargets.push(site);
13333
+ }
13334
+ }
13335
+ return { present: true, hostingTargets };
13336
+ }
13337
+ async function collectExtraReferenceSources(workspaceRoot) {
13338
+ const collected = [];
13339
+ for (const candidate of EXTRA_REFERENCE_SOURCES) {
13340
+ const absolute = join10(workspaceRoot, candidate);
13341
+ let stats;
13342
+ try {
13343
+ stats = await stat10(absolute);
13344
+ } catch {
13345
+ continue;
13346
+ }
13347
+ if (stats.isFile()) {
13348
+ collected.push(candidate);
13349
+ } else if (stats.isDirectory()) {
13350
+ const entries = await readdir8(absolute, { withFileTypes: true });
13351
+ for (const entry of entries) {
13352
+ if (entry.isFile() && /\.ya?ml$/.test(entry.name)) {
13353
+ collected.push(join10(candidate, entry.name));
13354
+ }
13355
+ }
13356
+ }
13357
+ }
13358
+ const rootEntries = await readdir8(workspaceRoot, { withFileTypes: true }).catch(() => []);
13359
+ for (const entry of rootEntries) {
13360
+ if (entry.isFile() && entry.name.endsWith(".sh")) {
13361
+ collected.push(entry.name);
13362
+ }
13363
+ }
13364
+ return collected.sort((a, b) => a.localeCompare(b));
13365
+ }
13366
+ async function collectPackageScriptReferences(workspaceRoot) {
13367
+ const parsed = await readJsonFile(join10(workspaceRoot, "package.json"));
13368
+ const scripts = parsed?.["scripts"];
13369
+ const collected = [];
13370
+ if (scripts !== null && typeof scripts === "object") {
13371
+ for (const [scriptName, value] of Object.entries(scripts)) {
13372
+ if (typeof value === "string") {
13373
+ for (const reference of scanTargetReferences({ text: value, sourceFile: "package.json", sourceTarget: scriptName })) {
13374
+ collected.push(reference);
13375
+ }
13376
+ }
13377
+ }
13378
+ }
13379
+ return collected;
13380
+ }
13381
+ async function inspectWorkspace(input) {
13382
+ const workspaceRoot = resolve3(input.workspaceRoot);
13383
+ const configPaths = await collectProjectConfigPaths({ workspaceRoot, relativeDir: "", depth: 0 });
13384
+ const projects = [];
13385
+ const references = [];
13386
+ for (const configFile of [...configPaths].sort((a, b) => a.localeCompare(b))) {
13387
+ const project = await inspectProject({ workspaceRoot, configFile });
13388
+ if (project === void 0) {
13389
+ continue;
13390
+ }
13391
+ projects.push(project);
13392
+ for (const target of project.targets) {
13393
+ for (const command of target.commands) {
13394
+ for (const reference of scanTargetReferences({ text: command, sourceFile: configFile, sourceTarget: target.name })) {
13395
+ references.push(reference);
13396
+ }
13397
+ }
13398
+ }
13399
+ }
13400
+ for (const sourceFile of await collectExtraReferenceSources(workspaceRoot)) {
13401
+ const text = await readFile11(join10(workspaceRoot, sourceFile), "utf8").catch(() => void 0);
13402
+ if (text !== void 0) {
13403
+ for (const reference of scanDocumentTargetReferences({ text, sourceFile })) {
13404
+ references.push(reference);
13405
+ }
13406
+ }
13407
+ }
13408
+ for (const reference of await collectPackageScriptReferences(workspaceRoot)) {
13409
+ references.push(reference);
13410
+ }
13411
+ return {
13412
+ workspaceRoot,
13413
+ projects,
13414
+ references,
13415
+ firebase: await inspectFirebaseConfig(workspaceRoot),
13416
+ nx: await inspectNxConfig(workspaceRoot)
13417
+ };
13418
+ }
13419
+ function pathEscapesWorkspaceRoot(config) {
13420
+ const { workspaceRoot, candidate } = config;
13421
+ const resolved = resolve3(workspaceRoot, candidate);
13422
+ const rel = relative6(workspaceRoot, resolved);
13423
+ return rel.startsWith("..");
13424
+ }
13425
+
13426
+ // packages/dbx-cli/validate/src/lib/workspace-validate/types.ts
13427
+ var WORKSPACE_RULE_GROUPS = ["targets", "deploy"];
13428
+ var DEPLOY_TARGET_PREFIX = "ci-deploy-";
13429
+
13430
+ // packages/dbx-cli/validate/src/lib/workspace-validate/rules.ts
13431
+ var FILE_REPLACEMENT_EXECUTORS = /* @__PURE__ */ new Set(["@nx/angular:application", "@nx/angular:browser-esbuild", "@angular/build:application", "@angular-devkit/build-angular:application", "@angular-devkit/build-angular:browser"]);
13432
+ var ESBUILD_CONFIG_EXECUTORS = /* @__PURE__ */ new Set(["@nx/esbuild:esbuild"]);
13433
+ var PLUGIN_INFERRED_TARGETS = [
13434
+ { plugin: "@nx/eslint/plugin", targets: ["lint"] },
13435
+ { plugin: "@nx/vitest", targets: ["test", "test-ci"] },
13436
+ { plugin: "@nx/vite", targets: ["build", "serve", "preview", "test"] },
13437
+ { plugin: "@nx/jest", targets: ["test"] },
13438
+ { plugin: "@nx/webpack/plugin", targets: ["build", "serve", "preview"] },
13439
+ { plugin: "@nx/rollup/plugin", targets: ["build"] },
13440
+ { plugin: "@nx/js/typescript", targets: ["typecheck", "build"] },
13441
+ { plugin: "@nx/playwright/plugin", targets: ["e2e", "e2e-ci"] },
13442
+ { plugin: "@nx/cypress/plugin", targets: ["e2e", "component-test"] },
13443
+ { plugin: "@nx/next/plugin", targets: ["build", "dev", "start", "serve-static"] }
13444
+ ];
13445
+ var FILE_EXTENSION_PATTERN = /\.(?:ts|tsx|js|mjs|cjs|json|sh|ya?ml|md|html|scss|css)$/;
13446
+ var DEPLOY_LANE_PATTERN = new RegExp(`^${DEPLOY_TARGET_PREFIX}([a-z0-9]+)$`);
13447
+ function pushViolation11(buffer, violation) {
13448
+ const severity = violation.severity ?? "error";
13449
+ const filled = {
13450
+ code: violation.code,
13451
+ severity,
13452
+ message: violation.message,
13453
+ group: violation.group,
13454
+ project: violation.project,
13455
+ file: violation.file,
13456
+ line: violation.line,
13457
+ remediation: attachRemediation(violation.code)
13458
+ };
13459
+ buffer.push(filled);
13460
+ }
13461
+ function looksLikeProjectName(token) {
13462
+ return !token.includes("/") && !token.includes("\\") && !FILE_EXTENSION_PATTERN.test(token);
13463
+ }
13464
+ function isExemptTargetName(config) {
13465
+ const { targetName, nx } = config;
13466
+ let result = nx.targetDefaultNames.includes(targetName);
13467
+ if (!result) {
13468
+ const plugins = new Set(nx.plugins);
13469
+ result = PLUGIN_INFERRED_TARGETS.some((entry) => plugins.has(entry.plugin) && entry.targets.includes(targetName));
13470
+ }
13471
+ return result;
13472
+ }
13473
+ function substituteOutputTokens(config) {
13474
+ const { entry, options, projectRoot } = config;
13475
+ let unresolved = false;
13476
+ const withOptions = entry.replaceAll(/\{options\.(\w+)\}/g, (_match, key) => {
13477
+ const value = options[key];
13478
+ let replacement;
13479
+ if (typeof value === "string") {
13480
+ replacement = value;
13481
+ } else if (typeof value === "number" || typeof value === "boolean") {
13482
+ replacement = String(value);
13483
+ } else {
13484
+ unresolved = true;
13485
+ replacement = "";
13486
+ }
13487
+ return replacement;
13488
+ });
13489
+ const projectRootValue = projectRoot === "" ? "." : projectRoot;
13490
+ return unresolved ? void 0 : withOptions.replaceAll("{workspaceRoot}", ".").replaceAll("{projectRoot}", projectRootValue);
13491
+ }
13492
+ function environmentSelectionState(config) {
13493
+ const { executor, configuration } = config;
13494
+ let result;
13495
+ if (executor !== void 0 && FILE_REPLACEMENT_EXECUTORS.has(executor)) {
13496
+ result = configuration.fileReplacements.length > 0 ? "ok" : "missing";
13497
+ } else if (executor !== void 0 && ESBUILD_CONFIG_EXECUTORS.has(executor)) {
13498
+ result = configuration.esbuildConfig === void 0 ? "missing" : "ok";
13499
+ } else {
13500
+ result = "unknown";
13501
+ }
13502
+ return result;
13503
+ }
13504
+ function environmentSelectionOptionName(executor) {
13505
+ return executor !== void 0 && ESBUILD_CONFIG_EXECUTORS.has(executor) ? "esbuildConfig" : "fileReplacements";
13506
+ }
13507
+ function indexTargets(project) {
13508
+ const map = /* @__PURE__ */ new Map();
13509
+ for (const target of project.targets) {
13510
+ map.set(target.name, target);
13511
+ }
13512
+ return map;
13513
+ }
13514
+ function checkTargetReferences(config) {
13515
+ const { inspection, targetsByProject, violations } = config;
13516
+ const group = "targets";
13517
+ for (const reference of inspection.references) {
13518
+ const targets = targetsByProject.get(reference.project);
13519
+ if (targets === void 0) {
13520
+ if (looksLikeProjectName(reference.project)) {
13521
+ pushViolation11(violations, {
13522
+ code: "WORKSPACE_TARGET_REF_PROJECT_MISSING",
13523
+ message: `\`${reference.raw}\` references project \`${reference.project}\`, which the workspace does not declare.`,
13524
+ group,
13525
+ project: reference.project,
13526
+ file: reference.sourceFile,
13527
+ line: reference.line
13528
+ });
13529
+ }
13530
+ continue;
13531
+ }
13532
+ const target = targets.get(reference.target);
13533
+ if (target === void 0) {
13534
+ if (!isExemptTargetName({ targetName: reference.target, nx: inspection.nx })) {
13535
+ pushViolation11(violations, {
13536
+ code: "WORKSPACE_TARGET_REF_TARGET_MISSING",
13537
+ message: `\`${reference.raw}\` references target \`${reference.target}\` on project \`${reference.project}\`, which declares no such target.`,
13538
+ group,
13539
+ project: reference.project,
13540
+ file: reference.sourceFile,
13541
+ line: reference.line
13542
+ });
13543
+ }
13544
+ continue;
13545
+ }
13546
+ if (reference.configuration !== void 0 && target.configurations.length > 0 && !target.configurations.some((configuration) => configuration.name === reference.configuration)) {
13547
+ const declared = target.configurations.map((configuration) => configuration.name).join(", ");
13548
+ const fallback = target.defaultConfiguration === void 0 ? "no configuration at all \u2014 the target runs with its base options, so every configuration-only option is silently dropped" : `\`${target.defaultConfiguration}\` (this target's \`defaultConfiguration\`)`;
13549
+ pushViolation11(violations, {
13550
+ code: "WORKSPACE_TARGET_REF_CONFIGURATION_MISSING",
13551
+ message: `\`${reference.raw}\` requests configuration \`${reference.configuration}\` of \`${reference.project}:${reference.target}\`, which declares only [${declared}]. Nx does not error here \u2014 it silently builds ${fallback}.`,
13552
+ group,
13553
+ project: reference.project,
13554
+ file: reference.sourceFile,
13555
+ line: reference.line
13556
+ });
13557
+ }
13558
+ }
13559
+ }
13560
+ function checkTargetOutputs(config) {
13561
+ const { inspection, violations } = config;
13562
+ const group = "targets";
13563
+ for (const project of inspection.projects) {
13564
+ for (const target of project.targets) {
13565
+ for (const entry of target.outputs) {
13566
+ const substituted = substituteOutputTokens({ entry, options: target.options, projectRoot: project.root });
13567
+ if (substituted === void 0) {
13568
+ continue;
13569
+ }
13570
+ if (pathEscapesWorkspaceRoot({ workspaceRoot: inspection.workspaceRoot, candidate: substituted })) {
13571
+ pushViolation11(violations, {
13572
+ code: "WORKSPACE_TARGET_OUTPUT_ESCAPES_ROOT",
13573
+ message: `Target \`${project.name}:${target.name}\` declares output \`${entry}\`, which resolves to \`${substituted}\` \u2014 outside the workspace root. Nx cannot cache it and reports the task as flaky instead of failing.`,
13574
+ group,
13575
+ project: project.name,
13576
+ file: project.configFile,
13577
+ line: void 0
13578
+ });
13579
+ }
13580
+ }
13581
+ }
13582
+ }
13583
+ }
13584
+ function checkDeployLaneEnvironmentSelection(config) {
13585
+ const { inspection, targetsByProject, violations } = config;
13586
+ const group = "deploy";
13587
+ for (const project of inspection.projects) {
13588
+ const targets = targetsByProject.get(project.name);
13589
+ if (targets === void 0) {
13590
+ continue;
13591
+ }
13592
+ const deployTargetNames = new Set(project.targets.filter((target) => target.name.startsWith(DEPLOY_TARGET_PREFIX)).map((target) => target.name));
13593
+ for (const reference of inspection.references) {
13594
+ if (reference.sourceFile !== project.configFile || reference.sourceTarget === void 0 || !deployTargetNames.has(reference.sourceTarget) || reference.project !== project.name || reference.configuration === void 0) {
13595
+ continue;
13596
+ }
13597
+ const buildTarget = targets.get(reference.target);
13598
+ const configuration = buildTarget?.configurations.find((entry) => entry.name === reference.configuration);
13599
+ if (buildTarget === void 0 || configuration === void 0) {
13600
+ continue;
13601
+ }
13602
+ if (environmentSelectionState({ executor: buildTarget.executor, configuration }) === "missing") {
13603
+ pushViolation11(violations, {
13604
+ code: "WORKSPACE_DEPLOY_LANE_NO_ENVIRONMENT_SELECTION",
13605
+ message: `\`${project.name}:${reference.sourceTarget}\` builds \`${reference.target}:${configuration.name}\`, but that configuration declares no \`${environmentSelectionOptionName(buildTarget.executor)}\` \u2014 the lane deploys the unreplaced \`environment.ts\`.`,
13606
+ group,
13607
+ project: project.name,
13608
+ file: project.configFile,
13609
+ line: void 0
13610
+ });
13611
+ }
13612
+ }
13613
+ }
13614
+ }
13615
+ function checkConfigurationPaths(config) {
13616
+ const { inspection, violations } = config;
13617
+ const group = "deploy";
13618
+ for (const project of inspection.projects) {
13619
+ for (const target of project.targets) {
13620
+ for (const configuration of target.configurations) {
13621
+ for (const replacement of configuration.fileReplacements) {
13622
+ if (!replacement.replaceExists) {
13623
+ pushViolation11(violations, {
13624
+ code: "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_MISSING",
13625
+ message: `\`${project.name}:${target.name}:${configuration.name}\` replaces \`${replacement.replace}\`, which does not exist.`,
13626
+ group,
13627
+ project: project.name,
13628
+ file: project.configFile,
13629
+ line: void 0
13630
+ });
13631
+ }
13632
+ if (!replacement.withExists) {
13633
+ pushViolation11(violations, {
13634
+ code: "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_MISSING",
13635
+ message: `\`${project.name}:${target.name}:${configuration.name}\` replaces with \`${replacement.with}\`, which does not exist.`,
13636
+ group,
13637
+ project: project.name,
13638
+ file: project.configFile,
13639
+ line: void 0
13640
+ });
13641
+ }
13642
+ }
13643
+ if (configuration.esbuildConfig !== void 0 && !configuration.esbuildConfigExists) {
13644
+ pushViolation11(violations, {
13645
+ code: "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_MISSING",
13646
+ message: `\`${project.name}:${target.name}:${configuration.name}\` sets \`esbuildConfig\` to \`${configuration.esbuildConfig}\`, which does not exist.`,
13647
+ group,
13648
+ project: project.name,
13649
+ file: project.configFile,
13650
+ line: void 0
13651
+ });
13652
+ }
13653
+ }
13654
+ }
13655
+ }
13656
+ }
13657
+ function checkUnreferencedEnvironmentFiles(config) {
13658
+ const { inspection, violations } = config;
13659
+ const group = "deploy";
13660
+ for (const project of inspection.projects) {
13661
+ const referenced = new Set(project.referencedEnvironmentFiles);
13662
+ for (const environmentFile of project.environmentFiles) {
13663
+ if (!referenced.has(environmentFile)) {
13664
+ pushViolation11(violations, {
13665
+ code: "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_UNREFERENCED",
13666
+ message: `\`${environmentFile}\` is not selected by any build configuration of \`${project.name}\` \u2014 it is edited and reviewed but never compiled.`,
13667
+ severity: "warning",
13668
+ group,
13669
+ project: project.name,
13670
+ file: project.configFile,
13671
+ line: void 0
13672
+ });
13673
+ }
13674
+ }
13675
+ }
13676
+ }
13677
+ function checkHostingTargets(config) {
13678
+ const { inspection, violations } = config;
13679
+ const group = "deploy";
13680
+ if (!inspection.firebase.present) {
13681
+ return;
13682
+ }
13683
+ const declared = new Set(inspection.firebase.hostingTargets);
13684
+ for (const project of inspection.projects) {
13685
+ for (const target of project.targets) {
13686
+ for (const command of target.commands) {
13687
+ for (const hostingTarget of scanHostingTargets(command)) {
13688
+ if (!declared.has(hostingTarget)) {
13689
+ pushViolation11(violations, {
13690
+ code: "WORKSPACE_DEPLOY_HOSTING_TARGET_MISSING",
13691
+ message: `\`${project.name}:${target.name}\` deploys \`hosting:${hostingTarget}\`, which \`firebase.json\` does not declare (declared: [${Array.from(declared).join(", ") || "none"}]).`,
13692
+ group,
13693
+ project: project.name,
13694
+ file: project.configFile,
13695
+ line: void 0
13696
+ });
13697
+ }
13698
+ }
13699
+ }
13700
+ }
13701
+ }
13702
+ }
13703
+ function checkDeployLaneSymmetry(config) {
13704
+ const { inspection, violations } = config;
13705
+ const group = "deploy";
13706
+ const lanesByProject = /* @__PURE__ */ new Map();
13707
+ const allLanes = /* @__PURE__ */ new Set();
13708
+ for (const project of inspection.projects) {
13709
+ const lanes = /* @__PURE__ */ new Set();
13710
+ for (const target of project.targets) {
13711
+ const match = DEPLOY_LANE_PATTERN.exec(target.name);
13712
+ if (match !== null) {
13713
+ lanes.add(match[1]);
13714
+ allLanes.add(match[1]);
13715
+ }
13716
+ }
13717
+ if (lanes.size > 0) {
13718
+ lanesByProject.set(project.name, lanes);
13719
+ }
13720
+ }
13721
+ if (lanesByProject.size < 2) {
13722
+ return;
13723
+ }
13724
+ for (const project of inspection.projects) {
13725
+ const lanes = lanesByProject.get(project.name);
13726
+ if (lanes === void 0) {
13727
+ continue;
13728
+ }
13729
+ for (const lane of Array.from(allLanes).sort((a, b) => a.localeCompare(b))) {
13730
+ if (!lanes.has(lane)) {
13731
+ pushViolation11(violations, {
13732
+ code: "WORKSPACE_DEPLOY_LANE_ASYMMETRIC",
13733
+ message: `\`${project.name}\` deploys [${Array.from(lanes).sort((a, b) => a.localeCompare(b)).join(", ")}] but has no \`${DEPLOY_TARGET_PREFIX}${lane}\`, while a sibling project deploys \`${lane}\`.`,
13734
+ severity: "warning",
13735
+ group,
13736
+ project: project.name,
13737
+ file: project.configFile,
13738
+ line: void 0
13739
+ });
13740
+ }
13741
+ }
13742
+ }
13743
+ }
13744
+ function runRules9(input) {
13745
+ const { inspection, groups } = input;
13746
+ const violations = [];
13747
+ const targetsByProject = /* @__PURE__ */ new Map();
13748
+ for (const project of inspection.projects) {
13749
+ targetsByProject.set(project.name, indexTargets(project));
13750
+ }
13751
+ if (groups.includes("targets")) {
13752
+ checkTargetReferences({ inspection, targetsByProject, violations });
13753
+ checkTargetOutputs({ inspection, violations });
13754
+ }
13755
+ if (groups.includes("deploy")) {
13756
+ checkDeployLaneEnvironmentSelection({ inspection, targetsByProject, violations });
13757
+ checkConfigurationPaths({ inspection, violations });
13758
+ checkUnreferencedEnvironmentFiles({ inspection, violations });
13759
+ checkHostingTargets({ inspection, violations });
13760
+ checkDeployLaneSymmetry({ inspection, violations });
13761
+ }
13762
+ return violations;
13763
+ }
13764
+
13765
+ // packages/dbx-cli/validate/src/lib/workspace-validate/validate.ts
13766
+ function validateWorkspace(input) {
13767
+ const { inspection } = input;
13768
+ const groups = input.groups === void 0 || input.groups.length === 0 ? WORKSPACE_RULE_GROUPS : input.groups;
13769
+ const violations = runRules9({ inspection, groups });
13770
+ let errorCount = 0;
13771
+ let warningCount = 0;
13772
+ for (const violation of violations) {
13773
+ if (violation.severity === "error") {
13774
+ errorCount += 1;
13775
+ } else {
13776
+ warningCount += 1;
13777
+ }
13778
+ }
13779
+ return {
13780
+ violations,
13781
+ errorCount,
13782
+ warningCount,
13783
+ projectsChecked: inspection.projects.length,
13784
+ referencesChecked: inspection.references.length,
13785
+ groups
13786
+ };
13787
+ }
13788
+
12742
13789
  // packages/dbx-cli/validate/src/lib/model-list-component/index.ts
12743
13790
  var model_list_component_exports = {};
12744
13791
  __export(model_list_component_exports, {
@@ -12749,8 +13796,8 @@ __export(model_list_component_exports, {
12749
13796
  });
12750
13797
 
12751
13798
  // packages/dbx-cli/model-test/src/lib/fixture/inspect.ts
12752
- import { readFile as readFile11 } from "node:fs/promises";
12753
- import { join as join10 } from "node:path";
13799
+ import { readFile as readFile12 } from "node:fs/promises";
13800
+ import { join as join11 } from "node:path";
12754
13801
 
12755
13802
  // packages/dbx-cli/model-test/src/lib/fixture/extract.ts
12756
13803
  import { Node as Node16, Project as Project9, SyntaxKind as SyntaxKind7 } from "ts-morph";
@@ -13247,21 +14294,21 @@ function collectIdentityImports(sourceFile) {
13247
14294
  // packages/dbx-cli/model-test/src/lib/fixture/inspect.ts
13248
14295
  var FIXTURE_RELATIVE_PATH = "src/test/fixture.ts";
13249
14296
  async function inspectAppFixtures(apiAbs, apiRel) {
13250
- const absolutePath = join10(apiAbs, FIXTURE_RELATIVE_PATH);
14297
+ const absolutePath = join11(apiAbs, FIXTURE_RELATIVE_PATH);
13251
14298
  const relativePath = `${apiRel}/${FIXTURE_RELATIVE_PATH}`;
13252
- const text = await readFile11(absolutePath, "utf8");
14299
+ const text = await readFile12(absolutePath, "utf8");
13253
14300
  return extractAppFixturesFromText({ text, fixturePath: relativePath });
13254
14301
  }
13255
14302
 
13256
14303
  // packages/dbx-cli/validate/src/lib/model-list-component/extract.ts
13257
- import { readdir as readdir8, readFile as readFile12, stat as stat10 } from "node:fs/promises";
13258
- import { basename as basename5, join as join11, relative as relative6, sep as sep7 } from "node:path";
14304
+ import { readdir as readdir9, readFile as readFile13, stat as stat11 } from "node:fs/promises";
14305
+ import { basename as basename6, join as join12, relative as relative7, sep as sep7 } from "node:path";
13259
14306
  import { Node as Node17, Project as Project10 } from "ts-morph";
13260
14307
  var MODEL_SUBPATH = "src/lib/model";
13261
14308
  var MODEL_SUBPATH_PREFIX = `${MODEL_SUBPATH}/`;
13262
14309
  var RESERVED_NAMES = RESERVED_MODEL_FOLDERS.map((r) => r.name);
13263
14310
  async function extractComponentModels(componentAbs) {
13264
- const modelRoot = join11(componentAbs, MODEL_SUBPATH);
14311
+ const modelRoot = join12(componentAbs, MODEL_SUBPATH);
13265
14312
  const folders = await listFolders(modelRoot);
13266
14313
  if (folders === void 0) {
13267
14314
  const result2 = { modelRoot, models: [], skipped: [], unidentifiedFolders: [] };
@@ -13351,13 +14398,13 @@ function projectRich(model, folder) {
13351
14398
  }
13352
14399
  async function fallbackForFolder(input) {
13353
14400
  const { modelRoot, componentAbs, folder, project } = input;
13354
- const folderAbs = join11(modelRoot, folder);
14401
+ const folderAbs = join12(modelRoot, folder);
13355
14402
  const mainFile = await readMainFile(folderAbs, folder);
13356
14403
  if (mainFile === void 0) return void 0;
13357
14404
  const sourceFile = project.createSourceFile(mainFile.fileName, mainFile.text, { overwrite: true });
13358
14405
  const identity = findIdentityIn(sourceFile);
13359
14406
  if (!identity) return void 0;
13360
- const sourceFileRel = relative6(componentAbs, mainFile.absPath).split(sep7).join("/");
14407
+ const sourceFileRel = relative7(componentAbs, mainFile.absPath).split(sep7).join("/");
13361
14408
  return {
13362
14409
  folder,
13363
14410
  modelName: deriveModelName(identity.constName, folder),
@@ -13373,13 +14420,13 @@ async function listFolders(modelRoot) {
13373
14420
  let result;
13374
14421
  let isDir = false;
13375
14422
  try {
13376
- const stats = await stat10(modelRoot);
14423
+ const stats = await stat11(modelRoot);
13377
14424
  isDir = stats.isDirectory();
13378
14425
  } catch {
13379
14426
  isDir = false;
13380
14427
  }
13381
14428
  if (isDir) {
13382
- const entries = await readdir8(modelRoot, { withFileTypes: true });
14429
+ const entries = await readdir9(modelRoot, { withFileTypes: true });
13383
14430
  const out = [];
13384
14431
  for (const entry of entries) {
13385
14432
  if (entry.isDirectory()) out.push(entry.name);
@@ -13390,11 +14437,11 @@ async function listFolders(modelRoot) {
13390
14437
  return result;
13391
14438
  }
13392
14439
  async function readMainFile(folderAbs, folder) {
13393
- const candidate = join11(folderAbs, `${folder}.ts`);
14440
+ const candidate = join12(folderAbs, `${folder}.ts`);
13394
14441
  let result;
13395
14442
  try {
13396
- const text = await readFile12(candidate, "utf8");
13397
- result = { absPath: candidate, fileName: basename5(candidate), text };
14443
+ const text = await readFile13(candidate, "utf8");
14444
+ result = { absPath: candidate, fileName: basename6(candidate), text };
13398
14445
  } catch {
13399
14446
  result = void 0;
13400
14447
  }
@@ -13539,21 +14586,21 @@ __export(model_api_list_app_exports, {
13539
14586
  });
13540
14587
 
13541
14588
  // packages/dbx-cli/validate/src/lib/model-api-list-app/extract.ts
13542
- import { readFile as readFile13 } from "node:fs/promises";
13543
- import { join as join12, relative as relative7, sep as sep8 } from "node:path";
14589
+ import { readFile as readFile14 } from "node:fs/promises";
14590
+ import { join as join13, relative as relative8, sep as sep8 } from "node:path";
13544
14591
  var API_SUFFIX2 = ".api.ts";
13545
14592
  var MODEL_SUBPATH2 = "src/lib";
13546
14593
  async function extractApiList(input) {
13547
- const modelRoot = join12(input.componentAbs, MODEL_SUBPATH2);
14594
+ const modelRoot = join13(input.componentAbs, MODEL_SUBPATH2);
13548
14595
  const apiFiles = await collectFilesWithSuffix(modelRoot, API_SUFFIX2);
13549
14596
  const entries = [];
13550
14597
  const files = [];
13551
14598
  for (const filePath of apiFiles) {
13552
- const text = await readFile13(filePath, "utf8");
14599
+ const text = await readFile14(filePath, "utf8");
13553
14600
  if (!text.includes("callModelFirebaseFunctionMapFactory")) {
13554
14601
  continue;
13555
14602
  }
13556
- const sourceFileRel = relative7(input.componentAbs, filePath).split(sep8).join("/");
14603
+ const sourceFileRel = relative8(input.componentAbs, filePath).split(sep8).join("/");
13557
14604
  const extraction = extractCrudEntries({ name: sourceFileRel, text });
13558
14605
  const fileEntries = extraction.entries.map((entry) => ({ ...entry, sourceFile: sourceFileRel }));
13559
14606
  const counts = countVerbs(fileEntries);
@@ -13684,17 +14731,17 @@ __export(model_api_lookup_exports, {
13684
14731
  });
13685
14732
 
13686
14733
  // packages/dbx-cli/validate/src/lib/model-api-lookup/extract.ts
13687
- import { readFile as readFile15 } from "node:fs/promises";
13688
- import { join as join14, relative as relative9, sep as sep10 } from "node:path";
14734
+ import { readFile as readFile16 } from "node:fs/promises";
14735
+ import { join as join15, relative as relative10, sep as sep10 } from "node:path";
13689
14736
 
13690
14737
  // packages/dbx-cli/validate/src/lib/model-api-lookup/extract-actions.ts
13691
- import { readFile as readFile14 } from "node:fs/promises";
13692
- import { join as join13, relative as relative8, sep as sep9 } from "node:path";
14738
+ import { readFile as readFile15 } from "node:fs/promises";
14739
+ import { join as join14, relative as relative9, sep as sep9 } from "node:path";
13693
14740
  import { Project as Project11, SyntaxKind as SyntaxKind8 } from "ts-morph";
13694
14741
  var ACTION_SERVER_SUFFIX = ".action.server.ts";
13695
14742
  var COMMON_MODEL_SUBPATH = "src/app/common/model";
13696
14743
  async function buildActionLookup(apiAbs) {
13697
- const modelRoot = join13(apiAbs, COMMON_MODEL_SUBPATH);
14744
+ const modelRoot = join14(apiAbs, COMMON_MODEL_SUBPATH);
13698
14745
  const files = await collectFilesWithSuffix(modelRoot, ACTION_SERVER_SUFFIX);
13699
14746
  const methodsByParams = /* @__PURE__ */ new Map();
13700
14747
  const factoriesByParams = /* @__PURE__ */ new Map();
@@ -13703,9 +14750,9 @@ async function buildActionLookup(apiAbs) {
13703
14750
  }
13704
14751
  const project = new Project11({ useInMemoryFileSystem: true, skipAddingFilesFromTsConfig: true });
13705
14752
  for (const filePath of files) {
13706
- const text = await readFile14(filePath, "utf8");
14753
+ const text = await readFile15(filePath, "utf8");
13707
14754
  const sourceFile = project.createSourceFile(filePath, text, { overwrite: true });
13708
- const sourceFileRel = relative8(apiAbs, filePath).split(sep9).join("/");
14755
+ const sourceFileRel = relative9(apiAbs, filePath).split(sep9).join("/");
13709
14756
  collectAbstractMethods({ sourceFile, sourceFileRel, methodsByParams });
13710
14757
  collectFactoryFunctions({ sourceFile, sourceFileRel, factoriesByParams });
13711
14758
  }
@@ -13865,13 +14912,13 @@ function mapDocFields(fields) {
13865
14912
  return result;
13866
14913
  }
13867
14914
  async function collectApiSources(componentAbs) {
13868
- const root = join14(componentAbs, COMPONENT_LIB_SUBPATH2);
14915
+ const root = join15(componentAbs, COMPONENT_LIB_SUBPATH2);
13869
14916
  const files = await collectFilesWithSuffix(root, API_SUFFIX3);
13870
14917
  const sources = [];
13871
14918
  for (const fileAbs of files) {
13872
- const text = await readFile15(fileAbs, "utf8");
14919
+ const text = await readFile16(fileAbs, "utf8");
13873
14920
  if (!text.includes("callModelFirebaseFunctionMapFactory")) continue;
13874
- const fileRel = relative9(componentAbs, fileAbs).split(sep10).join("/");
14921
+ const fileRel = relative10(componentAbs, fileAbs).split(sep10).join("/");
13875
14922
  sources.push({ fileAbs, fileRel, text });
13876
14923
  }
13877
14924
  return sources;
@@ -14019,14 +15066,14 @@ __export(server_actions_list_app_exports, {
14019
15066
  });
14020
15067
 
14021
15068
  // packages/dbx-cli/validate/src/lib/server-actions-list-app/extract.ts
14022
- import { readdir as readdir9, readFile as readFile16 } from "node:fs/promises";
14023
- import { dirname as dirname2, join as join15, relative as relative10, sep as sep11 } from "node:path";
15069
+ import { readdir as readdir10, readFile as readFile17 } from "node:fs/promises";
15070
+ import { dirname as dirname3, join as join16, relative as relative11, sep as sep11 } from "node:path";
14024
15071
  import { Node as Node18, Project as Project12 } from "ts-morph";
14025
15072
  var ACTION_SERVER_SUFFIX2 = ".action.server.ts";
14026
15073
  var COMMON_MODEL_SUBPATH2 = "src/app/common/model";
14027
15074
  var COMMON_BARREL_REL = "src/app/common/index.ts";
14028
15075
  async function extractServerActions(apiAbs, apiRel) {
14029
- const modelRoot = join15(apiAbs, COMMON_MODEL_SUBPATH2);
15076
+ const modelRoot = join16(apiAbs, COMMON_MODEL_SUBPATH2);
14030
15077
  const actionFiles = await collectFilesWithSuffix(modelRoot, ACTION_SERVER_SUFFIX2);
14031
15078
  if (actionFiles.length === 0) {
14032
15079
  return { modelRoot, entries: [], fixtureStatus: "ok" };
@@ -14046,14 +15093,14 @@ async function extractServerActions(apiAbs, apiRel) {
14046
15093
  }
14047
15094
  const entries = [];
14048
15095
  for (const filePath of actionFiles) {
14049
- const text = await readFile16(filePath, "utf8");
15096
+ const text = await readFile17(filePath, "utf8");
14050
15097
  const sourceFile = project.createSourceFile(filePath, text, { overwrite: true });
14051
15098
  const classes = findServerActionClasses(sourceFile);
14052
15099
  for (const className of classes) {
14053
15100
  const wiring = await inspectSiblingModule(filePath, className);
14054
15101
  const expectedGetterName = camelize(className);
14055
15102
  const fixtureCoverage = fixtureStatus === "ok" ? fixtureCoverageMap.get(expectedGetterName) ?? unmatchedFixtureCoverage(expectedGetterName) : void 0;
14056
- const sourceFileRel = relative10(apiAbs, filePath).split(sep11).join("/");
15103
+ const sourceFileRel = relative11(apiAbs, filePath).split(sep11).join("/");
14057
15104
  entries.push({
14058
15105
  className,
14059
15106
  sourceFile: sourceFileRel,
@@ -14095,14 +15142,14 @@ async function inspectSiblingModule(actionFilePath, className) {
14095
15142
  return { modulePath: moduleCandidates[0], providedByModule: false, exportedByModule: false };
14096
15143
  }
14097
15144
  async function findModuleCandidates(actionFilePath) {
14098
- const dir = dirname2(actionFilePath);
15145
+ const dir = dirname3(actionFilePath);
14099
15146
  const out = [];
14100
15147
  try {
14101
- const entries = await readdir9(dir, { withFileTypes: true });
15148
+ const entries = await readdir10(dir, { withFileTypes: true });
14102
15149
  for (const entry of entries) {
14103
15150
  if (!entry.isFile()) continue;
14104
15151
  if (!entry.name.endsWith(".module.ts")) continue;
14105
- out.push(join15(dir, entry.name));
15152
+ out.push(join16(dir, entry.name));
14106
15153
  }
14107
15154
  } catch {
14108
15155
  }
@@ -14112,7 +15159,7 @@ async function findModuleCandidates(actionFilePath) {
14112
15159
  async function safeRead(path) {
14113
15160
  let result;
14114
15161
  try {
14115
- result = await readFile16(path, "utf8");
15162
+ result = await readFile17(path, "utf8");
14116
15163
  } catch {
14117
15164
  result = void 0;
14118
15165
  }
@@ -14159,7 +15206,7 @@ function moduleArrayContains(decoratorArg, propertyName, className) {
14159
15206
  return false;
14160
15207
  }
14161
15208
  async function readCommonBarrelExports(apiAbs) {
14162
- const path = join15(apiAbs, COMMON_BARREL_REL);
15209
+ const path = join16(apiAbs, COMMON_BARREL_REL);
14163
15210
  const text = await safeRead(path);
14164
15211
  if (text === void 0) return /* @__PURE__ */ new Set();
14165
15212
  const project = new Project12({ useInMemoryFileSystem: true });
@@ -14338,8 +15385,19 @@ function formatReportAsJson4(report) {
14338
15385
 
14339
15386
  // packages/dbx-cli/validate/src/lib/dbx-asset-list-app/format.markdown.ts
14340
15387
  function formatReportAsMarkdown4(report) {
14341
- const basename6 = report.componentDir.split("/").pop() ?? report.componentDir;
14342
- const lines = [`# App assets \u2014 ${basename6}`, "", `Component: \`${report.componentDir}\``, `App: \`${report.apiDir}\``, "", `\`src/lib/assets.ts\`: ${formatBool(report.assetsFileExists)}`, `Barrel re-exports \`./assets\`: ${formatBool(report.barrelReExportsAssets)}`, `\`provideDbxAssetLoader()\` called in \`src/root.app.config.ts\`: ${formatBool(report.providerWiredInApp)}`, "", `## Assets (${report.assets.length})`];
15388
+ const basename7 = report.componentDir.split("/").pop() ?? report.componentDir;
15389
+ const lines = [
15390
+ `# App assets \u2014 ${basename7}`,
15391
+ "",
15392
+ `Component: \`${report.componentDir}\``,
15393
+ `App: \`${report.apiDir}\``,
15394
+ "",
15395
+ `\`src/lib/assets.ts\`: ${formatBool(report.assetsFileExists)}`,
15396
+ `Barrel re-exports \`./assets\`: ${formatBool(report.barrelReExportsAssets)}`,
15397
+ `\`provideDbxAssetLoader()\` called in \`src/root.app.config.ts\`: ${formatBool(report.providerWiredInApp)}`,
15398
+ "",
15399
+ `## Assets (${report.assets.length})`
15400
+ ];
14343
15401
  if (report.assets.length === 0) {
14344
15402
  lines.push("", "_None found._");
14345
15403
  } else {
@@ -14527,11 +15585,11 @@ function formatReportAsJson5(report) {
14527
15585
 
14528
15586
  // packages/dbx-cli/validate/src/lib/notification-m-list-app/format.markdown.ts
14529
15587
  function formatReportAsMarkdown5(report) {
14530
- const basename6 = report.componentDir.split("/").pop() ?? report.componentDir;
15588
+ const basename7 = report.componentDir.split("/").pop() ?? report.componentDir;
14531
15589
  const aggregatorRecordText = report.aggregatorRecordName ? code(report.aggregatorRecordName) : "_Not defined._";
14532
15590
  const templateConfigsFactoryText = report.templateConfigsArrayFactoryName ? code(report.templateConfigsArrayFactoryName) : "_Not defined._";
14533
15591
  const lines = [
14534
- `# App notifications \u2014 ${basename6}`,
15592
+ `# App notifications \u2014 ${basename7}`,
14535
15593
  "",
14536
15594
  `Component: \`${report.componentDir}\``,
14537
15595
  `API: \`${report.apiDir}\``,
@@ -14728,9 +15786,20 @@ function formatReportAsJson6(report) {
14728
15786
 
14729
15787
  // packages/dbx-cli/validate/src/lib/storagefile-m-list-app/format.markdown.ts
14730
15788
  function formatReportAsMarkdown6(report) {
14731
- const basename6 = report.componentDir.split("/").pop() ?? report.componentDir;
15789
+ const basename7 = report.componentDir.split("/").pop() ?? report.componentDir;
14732
15790
  const uploadServiceFactoryText = report.uploadServiceFactoryName ? code2(report.uploadServiceFactoryName) : "_Not defined._";
14733
- const lines = [`# App storagefiles \u2014 ${basename6}`, "", `Component: \`${report.componentDir}\``, `API: \`${report.apiDir}\``, "", `Upload service factory: ${uploadServiceFactoryText}`, `Wired via \`StorageFileInitializeFromUploadService\` provider: ${formatBool3(report.uploadServiceWiredInApi)}`, `Processing handler call present: ${formatBool3(report.processingHandlerWiredInApi)}`, "", `## Purposes (${report.purposes.length})`];
15791
+ const lines = [
15792
+ `# App storagefiles \u2014 ${basename7}`,
15793
+ "",
15794
+ `Component: \`${report.componentDir}\``,
15795
+ `API: \`${report.apiDir}\``,
15796
+ "",
15797
+ `Upload service factory: ${uploadServiceFactoryText}`,
15798
+ `Wired via \`StorageFileInitializeFromUploadService\` provider: ${formatBool3(report.uploadServiceWiredInApi)}`,
15799
+ `Processing handler call present: ${formatBool3(report.processingHandlerWiredInApi)}`,
15800
+ "",
15801
+ `## Purposes (${report.purposes.length})`
15802
+ ];
14734
15803
  if (report.purposes.length === 0) {
14735
15804
  lines.push("", "_None found._");
14736
15805
  } else {
@@ -14914,8 +15983,8 @@ __export(dbx_color_template_list_app_exports, {
14914
15983
  });
14915
15984
 
14916
15985
  // packages/dbx-cli/validate/src/lib/dbx-color-template-list-app/inspect.ts
14917
- import { readFile as readFile17 } from "node:fs/promises";
14918
- import { join as join16, relative as relative11, sep as sep12 } from "node:path";
15986
+ import { readFile as readFile18 } from "node:fs/promises";
15987
+ import { join as join17, relative as relative12, sep as sep12 } from "node:path";
14919
15988
  var CANDIDATE_RELPATHS = ["src/root.app.config.ts", "src/app.config.ts", "src/app.module.ts", "src/app/app.config.ts", "src/app/app.module.ts"];
14920
15989
  async function inspectColorTemplates(apiAbs, apiRel) {
14921
15990
  const present = [];
@@ -14925,7 +15994,7 @@ async function inspectColorTemplates(apiAbs, apiRel) {
14925
15994
  for (const candidate of candidates) {
14926
15995
  const text = await readFileIfExists2(candidate);
14927
15996
  if (text === void 0) continue;
14928
- const rel = relative11(apiAbs, candidate).split(sep12).join("/");
15997
+ const rel = relative12(apiAbs, candidate).split(sep12).join("/");
14929
15998
  present.push({ relPath: rel, text });
14930
15999
  }
14931
16000
  } catch (err) {
@@ -14942,25 +16011,25 @@ async function inspectColorTemplates(apiAbs, apiRel) {
14942
16011
  async function collectCandidatePaths(apiAbs) {
14943
16012
  const explicit = [];
14944
16013
  for (const rel of CANDIDATE_RELPATHS) {
14945
- explicit.push(join16(apiAbs, rel));
16014
+ explicit.push(join17(apiAbs, rel));
14946
16015
  }
14947
- const scanned = await scanProviderFiles(join16(apiAbs, "src"));
16016
+ const scanned = await scanProviderFiles(join17(apiAbs, "src"));
14948
16017
  return [...explicit, ...scanned];
14949
16018
  }
14950
16019
  async function scanProviderFiles(srcAbs) {
14951
- const { readdir: readdir10 } = await import("node:fs/promises");
16020
+ const { readdir: readdir11 } = await import("node:fs/promises");
14952
16021
  const out = [];
14953
16022
  const stack = [srcAbs];
14954
16023
  while (stack.length > 0) {
14955
16024
  const current = stack.pop();
14956
16025
  let entries;
14957
16026
  try {
14958
- entries = await readdir10(current, { withFileTypes: true });
16027
+ entries = await readdir11(current, { withFileTypes: true });
14959
16028
  } catch {
14960
16029
  continue;
14961
16030
  }
14962
16031
  for (const entry of entries) {
14963
- const full = join16(current, entry.name);
16032
+ const full = join17(current, entry.name);
14964
16033
  if (entry.isDirectory()) {
14965
16034
  stack.push(full);
14966
16035
  continue;
@@ -14975,7 +16044,7 @@ async function scanProviderFiles(srcAbs) {
14975
16044
  async function readFileIfExists2(absPath) {
14976
16045
  let result;
14977
16046
  try {
14978
- result = await readFile17(absPath, "utf8");
16047
+ result = await readFile18(absPath, "utf8");
14979
16048
  } catch (err) {
14980
16049
  const code3 = err.code;
14981
16050
  if (code3 !== "ENOENT" && code3 !== "ENOTDIR" && code3 !== "EISDIR") {
@@ -15679,11 +16748,12 @@ function matchKindForRadius(exact, raw) {
15679
16748
  if (exact.defaults.light === raw || exact.defaults.dark === raw) return "(exact-match design-system corner token).";
15680
16749
  return "(idiomatic full / pill radius \u2014 design system token).";
15681
16750
  }
15682
- function findExactRoleToken(registry, raw, role) {
16751
+ function findExactRoleToken(params) {
16752
+ const { registry, raw, role, isAllowed } = params;
15683
16753
  const candidates = registry.byRole.get(role) ?? [];
15684
16754
  let result;
15685
16755
  for (const entry of candidates) {
15686
- if (entry.defaults.light === raw || entry.defaults.dark === raw) {
16756
+ if ((entry.defaults.light === raw || entry.defaults.dark === raw) && (isAllowed === void 0 || isAllowed(entry))) {
15687
16757
  result = entry;
15688
16758
  break;
15689
16759
  }
@@ -15828,7 +16898,7 @@ var hardcodedRadius = (input) => {
15828
16898
  const colonIndex = input.scss.indexOf(":", match.index);
15829
16899
  if (colonIndex !== -1 && isInsideScssVarDeclaration(input.scss, colonIndex)) continue;
15830
16900
  const raw = match[1];
15831
- let exact = findExactRoleToken(input.tokenRegistry, raw, "radius");
16901
+ let exact = findExactRoleToken({ registry: input.tokenRegistry, raw, role: "radius" });
15832
16902
  if (exact === void 0 && FULL_RADIUS_SYNONYMS.has(raw)) {
15833
16903
  exact = input.tokenRegistry.findByCssVariable("--mat-sys-corner-full");
15834
16904
  }
@@ -15859,7 +16929,7 @@ var hardcodedShadow = (input) => {
15859
16929
  if (match.index === void 0) continue;
15860
16930
  const raw = match[1].trim();
15861
16931
  if (raw.startsWith("var(") || raw === "none") continue;
15862
- const exact = findExactRoleToken(input.tokenRegistry, raw, "elevation");
16932
+ const exact = findExactRoleToken({ registry: input.tokenRegistry, raw, role: "elevation" });
15863
16933
  const fix = exact === void 0 ? 'Hardcoded `box-shadow:` declarations should map to one of the `--mat-sys-level0..5` elevation tokens, or wrap the surface in `<dbx-content-box [elevate]="true">` so the shadow comes from the design system.' : `Replace this hardcoded shadow with \`box-shadow: var(${exact.cssVariable})\` or use \`<dbx-content-box [elevate]="true">\` to apply the elevation through the wrapper component.`;
15864
16934
  matches.push({
15865
16935
  id: "hardcoded-shadow",
@@ -15904,12 +16974,13 @@ var hardcodedHintColor = (input) => {
15904
16974
  return matches;
15905
16975
  };
15906
16976
  var HARDCODED_PADDING_RE = /(?:padding|margin)\s*(?:-(?:top|right|bottom|left))?\s*:\s*(\d+(?:\.\d+)?px)(?!\s*\))/gi;
16977
+ var DBX_PADDING_SCALE_RE = /^--dbx-padding-\d+$/;
15907
16978
  var hardcodedPadding = (input) => {
15908
16979
  const matches = [];
15909
16980
  for (const match of input.scss.matchAll(HARDCODED_PADDING_RE)) {
15910
16981
  if (match.index === void 0) continue;
15911
16982
  const raw = match[1];
15912
- const exact = findExactRoleToken(input.tokenRegistry, raw, "spacing");
16983
+ const exact = findExactRoleToken({ registry: input.tokenRegistry, raw, role: "spacing", isAllowed: (entry) => DBX_PADDING_SCALE_RE.test(entry.cssVariable) });
15913
16984
  if (exact !== void 0) {
15914
16985
  const utility = exact.utilityClasses?.[0];
15915
16986
  const fix = ["Replace `" + match[0].trim() + "` with `var(" + exact.cssVariable + ")`" + (utility === void 0 ? "" : ` (or use the \`${utility}\` utility class on the host element)`) + "."].join("\n");
@@ -16647,5 +17718,6 @@ export {
16647
17718
  storagefile_m_validate_folder_exports as storagefileValidateFolder,
16648
17719
  system_m_list_app_exports as systemListApp,
16649
17720
  system_m_validate_folder_exports as systemValidateFolder,
16650
- ui_smell_check_exports as uiSmellCheck
17721
+ ui_smell_check_exports as uiSmellCheck,
17722
+ workspace_validate_exports as workspaceValidate
16651
17723
  };