@dereekb/dbx-cli 13.42.0 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/eslint/index.esm.js +2 -2
  2. package/eslint/package.json +7 -7
  3. package/firebase-api-manifest/main.js +22 -4
  4. package/firebase-api-manifest/package.json +5 -5
  5. package/firestore-query-manifest/main.js +9 -7
  6. package/firestore-query-manifest/package.json +4 -4
  7. package/generate-firestore-indexes/main.js +3 -3
  8. package/generate-firestore-indexes/package.json +3 -3
  9. package/generate-mcp-manifest/package.json +4 -4
  10. package/generate-route-manifest/main.js +1 -1
  11. package/generate-route-manifest/package.json +3 -3
  12. package/index.esm.js +2835 -2002
  13. package/lint-cache/package.json +3 -3
  14. package/manifest-extract/index.esm.js +6 -12
  15. package/manifest-extract/package.json +9 -10
  16. package/model-test/index.esm.js +61 -63
  17. package/model-test/package.json +3 -3
  18. package/package.json +14 -14
  19. package/route/package.json +7 -7
  20. package/src/lib/doctor/build-drift.check.d.ts +21 -0
  21. package/src/lib/doctor/doctor.command.factory.d.ts +21 -2
  22. package/src/lib/doctor/firestore-session.check.d.ts +7 -1
  23. package/src/lib/doctor/index.d.ts +1 -0
  24. package/src/lib/firestore/firestore.sdk-identity.d.ts +193 -0
  25. package/src/lib/firestore/index.d.ts +1 -0
  26. package/src/lib/manifest/types.d.ts +21 -0
  27. package/src/lib/mcp-scan/manifest/css-utilities-schema.d.ts +2 -2
  28. package/src/lib/mcp-scan/manifest/dbx-docs-ui-examples-schema.d.ts +2 -2
  29. package/src/lib/mcp-scan/manifest/ui-components-schema.d.ts +2 -2
  30. package/src/lib/mcp-scan/scan/dbx-docs-ui-examples-extract.d.ts +1 -1
  31. package/src/lib/mcp-scan/scan/ui-components-extract.d.ts +1 -1
  32. package/src/lib/runner/build-stamp.d.ts +138 -0
  33. package/src/lib/runner/index.d.ts +1 -0
  34. package/src/lib/runner/run.d.ts +13 -0
  35. package/test/index.esm.js +19 -22
  36. package/test/package.json +11 -11
  37. package/validate/index.js +1069 -76
  38. package/validate/package.json +4 -4
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
 
@@ -2996,22 +3086,22 @@ function formatSeeAlso(ref) {
2996
3086
  // packages/dbx-cli/validate/src/lib/_core/validate-input.ts
2997
3087
  import { glob as fsGlob, readFile, stat } from "node:fs/promises";
2998
3088
  import { resolve, sep } from "node:path";
2999
- function ensurePathInsideCwd(relative12, cwd) {
3089
+ function ensurePathInsideCwd(relative13, cwd) {
3000
3090
  const cwdPrefix = cwd.endsWith(sep) ? cwd : cwd + sep;
3001
- const absolute = resolve(cwd, relative12);
3091
+ const absolute = resolve(cwd, relative13);
3002
3092
  if (!absolute.startsWith(cwdPrefix) && absolute !== cwd) {
3003
- throw new Error(`Path \`${relative12}\` resolves outside the server cwd and is not allowed.`);
3093
+ throw new Error(`Path \`${relative13}\` resolves outside the server cwd and is not allowed.`);
3004
3094
  }
3005
3095
  }
3006
3096
  async function resolveFolderPaths(config) {
3007
3097
  const { paths, glob, cwd } = config;
3008
3098
  const collected = [];
3009
3099
  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);
3100
+ const accept = (relative13) => {
3101
+ if (seen.has(relative13)) return;
3102
+ ensurePathInsideCwd(relative13, cwd);
3103
+ seen.add(relative13);
3104
+ collected.push(relative13);
3015
3105
  };
3016
3106
  if (paths) {
3017
3107
  for (const p of paths) {
@@ -3054,13 +3144,13 @@ async function resolveValidatorSources(config) {
3054
3144
  pathList.push(match);
3055
3145
  }
3056
3146
  }
3057
- for (const relative12 of pathList) {
3058
- if (seenNames.has(relative12)) continue;
3059
- ensurePathInsideCwd(relative12, cwd);
3060
- const absolute = resolve(cwd, relative12);
3147
+ for (const relative13 of pathList) {
3148
+ if (seenNames.has(relative13)) continue;
3149
+ ensurePathInsideCwd(relative13, cwd);
3150
+ const absolute = resolve(cwd, relative13);
3061
3151
  const text = await readFile(absolute, "utf8");
3062
- seenNames.add(relative12);
3063
- collected.push({ name: relative12, text });
3152
+ seenNames.add(relative13);
3153
+ collected.push({ name: relative13, text });
3064
3154
  }
3065
3155
  return collected;
3066
3156
  }
@@ -3115,12 +3205,12 @@ function formatRemediationBlock(hint) {
3115
3205
  return lines.join("\n");
3116
3206
  }
3117
3207
  function aggregateFolderRules(config) {
3118
- const { inspections, runRules: runRules9 } = config;
3208
+ const { inspections, runRules: runRules10 } = config;
3119
3209
  const violations = [];
3120
3210
  let errorCount = 0;
3121
3211
  let warningCount = 0;
3122
3212
  for (const inspection of inspections) {
3123
- const folderViolations = runRules9(inspection);
3213
+ const folderViolations = runRules10(inspection);
3124
3214
  for (const v of folderViolations) {
3125
3215
  violations.push(v);
3126
3216
  if (v.severity === "error") {
@@ -6335,6 +6425,11 @@ var RESERVED_MODEL_FOLDERS = [
6335
6425
  reason: "StorageFile is a canonical group from `@dereekb/firebase` with a richer layout than the base 5 files (group, upload, file, etc.); downstream projects extend rather than redeclare it.",
6336
6426
  recommendedTool: "dbx_storagefile_m_validate_folder"
6337
6427
  },
6428
+ {
6429
+ name: "formspace",
6430
+ reason: "FormSpace is a canonical group from `@dereekb/firebase` with a richer layout than the base 5 files (type, upload, task, util, etc.); a downstream project only registers its own types and does not redeclare the model.",
6431
+ recommendedTool: "dbx_model_validate_folder"
6432
+ },
6338
6433
  {
6339
6434
  name: "calendar",
6340
6435
  reason: "Calendar is a canonical group from `@dereekb/firebase` with a richer layout than the base 5 files (type, expand, ics, processing, etc.); downstream projects extend rather than redeclare it.",
@@ -12734,6 +12829,901 @@ function appendDiagnosticEntry(lines, d) {
12734
12829
  }
12735
12830
  }
12736
12831
 
12832
+ // packages/dbx-cli/validate/src/lib/workspace-validate/index.ts
12833
+ var workspace_validate_exports = {};
12834
+ __export(workspace_validate_exports, {
12835
+ DEPLOY_TARGET_PREFIX: () => DEPLOY_TARGET_PREFIX,
12836
+ WORKSPACE_RULE_GROUPS: () => WORKSPACE_RULE_GROUPS,
12837
+ WorkspaceValidateCode: () => WorkspaceValidateCode,
12838
+ formatResult: () => formatResult11,
12839
+ inspectWorkspace: () => inspectWorkspace,
12840
+ pathEscapesWorkspaceRoot: () => pathEscapesWorkspaceRoot,
12841
+ scanDocumentTargetReferences: () => scanDocumentTargetReferences,
12842
+ scanHostingTargets: () => scanHostingTargets,
12843
+ scanTargetReferences: () => scanTargetReferences,
12844
+ validateWorkspace: () => validateWorkspace
12845
+ });
12846
+
12847
+ // packages/dbx-cli/validate/src/lib/workspace-validate/codes.ts
12848
+ var WorkspaceValidateCode = /* @__PURE__ */ ((WorkspaceValidateCode2) => {
12849
+ WorkspaceValidateCode2["WORKSPACE_TARGET_REF_PROJECT_MISSING"] = "WORKSPACE_TARGET_REF_PROJECT_MISSING";
12850
+ WorkspaceValidateCode2["WORKSPACE_TARGET_REF_TARGET_MISSING"] = "WORKSPACE_TARGET_REF_TARGET_MISSING";
12851
+ WorkspaceValidateCode2["WORKSPACE_TARGET_REF_CONFIGURATION_MISSING"] = "WORKSPACE_TARGET_REF_CONFIGURATION_MISSING";
12852
+ WorkspaceValidateCode2["WORKSPACE_TARGET_OUTPUT_ESCAPES_ROOT"] = "WORKSPACE_TARGET_OUTPUT_ESCAPES_ROOT";
12853
+ WorkspaceValidateCode2["WORKSPACE_DEPLOY_LANE_NO_ENVIRONMENT_SELECTION"] = "WORKSPACE_DEPLOY_LANE_NO_ENVIRONMENT_SELECTION";
12854
+ WorkspaceValidateCode2["WORKSPACE_DEPLOY_ENVIRONMENT_FILE_MISSING"] = "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_MISSING";
12855
+ WorkspaceValidateCode2["WORKSPACE_DEPLOY_ENVIRONMENT_FILE_UNREFERENCED"] = "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_UNREFERENCED";
12856
+ WorkspaceValidateCode2["WORKSPACE_DEPLOY_HOSTING_TARGET_MISSING"] = "WORKSPACE_DEPLOY_HOSTING_TARGET_MISSING";
12857
+ WorkspaceValidateCode2["WORKSPACE_DEPLOY_LANE_ASYMMETRIC"] = "WORKSPACE_DEPLOY_LANE_ASYMMETRIC";
12858
+ return WorkspaceValidateCode2;
12859
+ })(WorkspaceValidateCode || {});
12860
+
12861
+ // packages/dbx-cli/validate/src/lib/workspace-validate/format.ts
12862
+ function formatResult11(result) {
12863
+ return formatFileGroupedResult({
12864
+ title: "Workspace convention validation",
12865
+ summary: `Checked ${result.projectsChecked} project(s) and ${result.referencesChecked} target reference(s) across group(s) [${result.groups.join(", ")}].`,
12866
+ innerKey: (violation) => violation.project,
12867
+ result
12868
+ });
12869
+ }
12870
+
12871
+ // packages/dbx-cli/validate/src/lib/workspace-validate/inspect.ts
12872
+ import { readdir as readdir8, readFile as readFile11, stat as stat10 } from "node:fs/promises";
12873
+ import { basename as basename5, dirname as dirname2, join as join10, relative as relative6, resolve as resolve3 } from "node:path";
12874
+
12875
+ // packages/dbx-cli/validate/src/lib/workspace-validate/references.ts
12876
+ var NX_SUBCOMMANDS = /* @__PURE__ */ new Set(["run", "run-many", "affected", "affected:graph", "affected:test", "affected:build", "graph", "dep-graph", "print-affected", "show", "list", "report", "migrate", "reset", "repair", "generate", "g", "add", "init", "import", "sync", "release", "format", "format:check", "format:write", "daemon", "connect", "login", "logout", "view-logs", "exec", "watch", "documentation", "help", "register", "record", "start-ci-run"]);
12877
+ var INTERPOLATION_PATTERN = /[$`{}()*?]|\\\\/;
12878
+ var TOKEN_LEADING_CHARS = `"'`;
12879
+ var TOKEN_TRAILING_CHARS = `"',;`;
12880
+ var PROJECT_TOKEN_PATTERN = /^[a-zA-Z0-9@][a-zA-Z0-9@/_.-]*$/;
12881
+ var TARGET_TOKEN_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/;
12882
+ function isFlag(token) {
12883
+ return token.startsWith("-");
12884
+ }
12885
+ function isResolvable(token) {
12886
+ return token.length > 0 && !INTERPOLATION_PATTERN.test(token);
12887
+ }
12888
+ function trimTokenPunctuation(raw) {
12889
+ let start = 0;
12890
+ let end = raw.length;
12891
+ while (start < end && TOKEN_LEADING_CHARS.includes(raw[start])) {
12892
+ start += 1;
12893
+ }
12894
+ while (end > start && TOKEN_TRAILING_CHARS.includes(raw[end - 1])) {
12895
+ end -= 1;
12896
+ }
12897
+ return raw.slice(start, end);
12898
+ }
12899
+ function tokenize(text) {
12900
+ const out = [];
12901
+ for (const raw of text.split(/\s+/)) {
12902
+ const token = trimTokenPunctuation(raw);
12903
+ if (token.length > 0) {
12904
+ out.push(token);
12905
+ }
12906
+ }
12907
+ return out;
12908
+ }
12909
+ function isNxBinary(token) {
12910
+ return token === "nx" || token.endsWith("/nx");
12911
+ }
12912
+ function changesToForeignDirectory(tokens) {
12913
+ let result = false;
12914
+ for (let i = 0; i < tokens.length - 1 && !result; i += 1) {
12915
+ if (tokens[i] === "cd") {
12916
+ const destination = tokens[i + 1];
12917
+ result = destination.startsWith("~") || destination.startsWith("/");
12918
+ }
12919
+ }
12920
+ return result;
12921
+ }
12922
+ function parseExplicitForm(tokens, runIndex) {
12923
+ let result;
12924
+ for (let i = runIndex + 1; i < tokens.length; i += 1) {
12925
+ const token = tokens[i];
12926
+ if (isFlag(token)) {
12927
+ continue;
12928
+ }
12929
+ if (isResolvable(token) && token.includes(":")) {
12930
+ const [project, target, configuration] = token.split(":");
12931
+ if (project && target && PROJECT_TOKEN_PATTERN.test(project) && TARGET_TOKEN_PATTERN.test(target)) {
12932
+ result = { project, target, configuration: configuration || void 0, raw: token };
12933
+ }
12934
+ }
12935
+ break;
12936
+ }
12937
+ return result;
12938
+ }
12939
+ function parseShorthandForm(tokens, nxIndex) {
12940
+ const positional = [];
12941
+ for (let i = nxIndex + 1; i < tokens.length && positional.length < 2; i += 1) {
12942
+ const token = tokens[i];
12943
+ if (!isFlag(token)) {
12944
+ positional.push(token);
12945
+ }
12946
+ }
12947
+ let result;
12948
+ const [target, project] = positional;
12949
+ 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)) {
12950
+ result = { project, target, raw: `${target} ${project}` };
12951
+ }
12952
+ return result;
12953
+ }
12954
+ function scanTargetReferences(input) {
12955
+ const { text, sourceFile, sourceTarget, line } = input;
12956
+ const references = [];
12957
+ const tokens = tokenize(text);
12958
+ if (changesToForeignDirectory(tokens)) {
12959
+ return references;
12960
+ }
12961
+ for (let i = 0; i < tokens.length; i += 1) {
12962
+ if (!isNxBinary(tokens[i])) {
12963
+ continue;
12964
+ }
12965
+ let firstPositional;
12966
+ let firstPositionalIndex = -1;
12967
+ for (let j = i + 1; j < tokens.length; j += 1) {
12968
+ if (!isFlag(tokens[j])) {
12969
+ firstPositional = tokens[j];
12970
+ firstPositionalIndex = j;
12971
+ break;
12972
+ }
12973
+ }
12974
+ if (firstPositional === void 0) {
12975
+ continue;
12976
+ }
12977
+ if (firstPositional === "run") {
12978
+ const parsed = parseExplicitForm(tokens, firstPositionalIndex);
12979
+ if (parsed) {
12980
+ references.push({ project: parsed.project, target: parsed.target, configuration: parsed.configuration, sourceFile, line, sourceTarget, raw: parsed.raw });
12981
+ }
12982
+ } else if (!NX_SUBCOMMANDS.has(firstPositional)) {
12983
+ const parsed = parseShorthandForm(tokens, i);
12984
+ if (parsed) {
12985
+ references.push({ project: parsed.project, target: parsed.target, configuration: void 0, sourceFile, line, sourceTarget, raw: parsed.raw });
12986
+ }
12987
+ }
12988
+ }
12989
+ return references;
12990
+ }
12991
+ function stripComment(line) {
12992
+ const trimmed = line.trimStart();
12993
+ let result;
12994
+ if (trimmed.startsWith("#")) {
12995
+ result = "";
12996
+ } else {
12997
+ const index = line.indexOf(" #");
12998
+ result = index === -1 ? line : line.slice(0, index);
12999
+ }
13000
+ return result;
13001
+ }
13002
+ function scanDocumentTargetReferences(input) {
13003
+ const { text, sourceFile } = input;
13004
+ const references = [];
13005
+ const lines = text.split("\n");
13006
+ for (const [index, line] of lines.entries()) {
13007
+ for (const reference of scanTargetReferences({ text: stripComment(line), sourceFile, line: index + 1 })) {
13008
+ references.push(reference);
13009
+ }
13010
+ }
13011
+ return references;
13012
+ }
13013
+ function scanHostingTargets(command) {
13014
+ const out = [];
13015
+ const pattern = /hosting:([a-zA-Z0-9_-]+)/g;
13016
+ let match = pattern.exec(command);
13017
+ while (match !== null) {
13018
+ out.push(match[1]);
13019
+ match = pattern.exec(command);
13020
+ }
13021
+ return out;
13022
+ }
13023
+
13024
+ // packages/dbx-cli/validate/src/lib/workspace-validate/inspect.ts
13025
+ var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", "dist", "tmp", "coverage", ".nx", ".git", ".firebase", ".angular", ".cache", "templates"]);
13026
+ var MAX_WALK_DEPTH = 4;
13027
+ var PROJECT_CONFIG_FILENAME = "project.json";
13028
+ var ENVIRONMENTS_SUBPATH = join10("src", "environments");
13029
+ var ENVIRONMENT_FILE_PATTERN = /^environment\..+\.ts$/;
13030
+ var EXTRA_REFERENCE_SOURCES = [".circleci/config.yml", ".circleci/config.yaml", ".github/workflows"];
13031
+ async function pathExists(path) {
13032
+ let result;
13033
+ try {
13034
+ await stat10(path);
13035
+ result = true;
13036
+ } catch {
13037
+ result = false;
13038
+ }
13039
+ return result;
13040
+ }
13041
+ async function readJsonFile(path) {
13042
+ let result;
13043
+ try {
13044
+ const text = await readFile11(path, "utf8");
13045
+ const parsed = JSON.parse(text);
13046
+ result = parsed !== null && typeof parsed === "object" ? parsed : void 0;
13047
+ } catch {
13048
+ result = void 0;
13049
+ }
13050
+ return result;
13051
+ }
13052
+ async function collectProjectConfigPaths(config) {
13053
+ const { workspaceRoot, relativeDir, depth } = config;
13054
+ const collected = [];
13055
+ const absoluteDir = join10(workspaceRoot, relativeDir);
13056
+ const entries = await readdir8(absoluteDir, { withFileTypes: true }).catch(() => []);
13057
+ for (const entry of entries) {
13058
+ if (entry.isFile() && entry.name === PROJECT_CONFIG_FILENAME) {
13059
+ collected.push(relativeDir === "" ? PROJECT_CONFIG_FILENAME : join10(relativeDir, PROJECT_CONFIG_FILENAME));
13060
+ } else if (entry.isDirectory() && depth < MAX_WALK_DEPTH && !SKIPPED_DIRECTORIES.has(entry.name) && !entry.name.startsWith("firebase-export-")) {
13061
+ const nested = await collectProjectConfigPaths({ workspaceRoot, relativeDir: relativeDir === "" ? entry.name : join10(relativeDir, entry.name), depth: depth + 1 });
13062
+ for (const path of nested) {
13063
+ collected.push(path);
13064
+ }
13065
+ }
13066
+ }
13067
+ return collected;
13068
+ }
13069
+ function extractCommands(options) {
13070
+ const collected = [];
13071
+ const single = options["command"];
13072
+ if (typeof single === "string") {
13073
+ collected.push(single);
13074
+ }
13075
+ const many = options["commands"];
13076
+ if (Array.isArray(many)) {
13077
+ for (const entry of many) {
13078
+ if (typeof entry === "string") {
13079
+ collected.push(entry);
13080
+ } else if (entry !== null && typeof entry === "object") {
13081
+ const command = entry["command"];
13082
+ if (typeof command === "string") {
13083
+ collected.push(command);
13084
+ }
13085
+ }
13086
+ }
13087
+ }
13088
+ return collected;
13089
+ }
13090
+ async function resolveFileReplacements(config) {
13091
+ const { workspaceRoot, raw } = config;
13092
+ const collected = [];
13093
+ if (Array.isArray(raw)) {
13094
+ for (const entry of raw) {
13095
+ if (entry === null || typeof entry !== "object") {
13096
+ continue;
13097
+ }
13098
+ const record = entry;
13099
+ const replace = record["replace"];
13100
+ const withPath = record["with"];
13101
+ if (typeof replace === "string" && typeof withPath === "string") {
13102
+ collected.push({
13103
+ replace,
13104
+ with: withPath,
13105
+ replaceExists: await pathExists(join10(workspaceRoot, replace)),
13106
+ withExists: await pathExists(join10(workspaceRoot, withPath))
13107
+ });
13108
+ }
13109
+ }
13110
+ }
13111
+ return collected;
13112
+ }
13113
+ async function resolveConfigurations(config) {
13114
+ const { workspaceRoot, raw } = config;
13115
+ const collected = [];
13116
+ if (raw !== null && typeof raw === "object") {
13117
+ for (const [name, value] of Object.entries(raw)) {
13118
+ const options = value !== null && typeof value === "object" ? value : {};
13119
+ const esbuildConfigValue = options["esbuildConfig"];
13120
+ const esbuildConfig = typeof esbuildConfigValue === "string" ? esbuildConfigValue : void 0;
13121
+ collected.push({
13122
+ name,
13123
+ fileReplacements: await resolveFileReplacements({ workspaceRoot, raw: options["fileReplacements"] }),
13124
+ esbuildConfig,
13125
+ esbuildConfigExists: esbuildConfig === void 0 ? false : await pathExists(join10(workspaceRoot, esbuildConfig))
13126
+ });
13127
+ }
13128
+ }
13129
+ return collected;
13130
+ }
13131
+ async function resolveTargets(config) {
13132
+ const { workspaceRoot, raw } = config;
13133
+ const collected = [];
13134
+ if (raw !== null && typeof raw === "object") {
13135
+ for (const [name, value] of Object.entries(raw)) {
13136
+ const target = value !== null && typeof value === "object" ? value : {};
13137
+ const executorValue = target["executor"];
13138
+ const defaultConfigurationValue = target["defaultConfiguration"];
13139
+ const optionsValue = target["options"];
13140
+ const options = optionsValue !== null && typeof optionsValue === "object" ? optionsValue : {};
13141
+ const outputsValue = target["outputs"];
13142
+ collected.push({
13143
+ name,
13144
+ executor: typeof executorValue === "string" ? executorValue : void 0,
13145
+ configurations: await resolveConfigurations({ workspaceRoot, raw: target["configurations"] }),
13146
+ defaultConfiguration: typeof defaultConfigurationValue === "string" ? defaultConfigurationValue : void 0,
13147
+ outputs: Array.isArray(outputsValue) ? outputsValue.filter((o) => typeof o === "string") : [],
13148
+ options,
13149
+ commands: extractCommands(options)
13150
+ });
13151
+ }
13152
+ }
13153
+ return collected;
13154
+ }
13155
+ async function collectEnvironmentFiles(config) {
13156
+ const { workspaceRoot, projectRoot } = config;
13157
+ const relativeDir = projectRoot === "" ? ENVIRONMENTS_SUBPATH : join10(projectRoot, ENVIRONMENTS_SUBPATH);
13158
+ const collected = [];
13159
+ const entries = await readdir8(join10(workspaceRoot, relativeDir), { withFileTypes: true }).catch(() => []);
13160
+ for (const entry of entries) {
13161
+ if (entry.isFile() && ENVIRONMENT_FILE_PATTERN.test(entry.name)) {
13162
+ collected.push(join10(relativeDir, entry.name));
13163
+ }
13164
+ }
13165
+ return collected.sort((a, b) => a.localeCompare(b));
13166
+ }
13167
+ var ENVIRONMENT_MENTION_PATTERN = /environment(?:\.[A-Za-z0-9_-]+)*\.ts/g;
13168
+ async function collectReferencedEnvironmentFiles(config) {
13169
+ const { workspaceRoot, targets, environmentFiles } = config;
13170
+ const referenced = /* @__PURE__ */ new Set();
13171
+ const esbuildConfigPaths = /* @__PURE__ */ new Set();
13172
+ for (const target of targets) {
13173
+ const baseEsbuildConfig = target.options["esbuildConfig"];
13174
+ if (typeof baseEsbuildConfig === "string") {
13175
+ esbuildConfigPaths.add(baseEsbuildConfig);
13176
+ }
13177
+ for (const configuration of target.configurations) {
13178
+ for (const replacement of configuration.fileReplacements) {
13179
+ referenced.add(replacement.with);
13180
+ }
13181
+ if (configuration.esbuildConfig !== void 0) {
13182
+ esbuildConfigPaths.add(configuration.esbuildConfig);
13183
+ }
13184
+ }
13185
+ }
13186
+ const mentionedBasenames = /* @__PURE__ */ new Set();
13187
+ for (const configPath of esbuildConfigPaths) {
13188
+ const text = await readFile11(join10(workspaceRoot, configPath), "utf8").catch(() => void 0);
13189
+ if (text === void 0) {
13190
+ continue;
13191
+ }
13192
+ for (const match of text.matchAll(ENVIRONMENT_MENTION_PATTERN)) {
13193
+ mentionedBasenames.add(match[0]);
13194
+ }
13195
+ }
13196
+ for (const environmentFile of environmentFiles) {
13197
+ if (mentionedBasenames.has(basename5(environmentFile))) {
13198
+ referenced.add(environmentFile);
13199
+ }
13200
+ }
13201
+ return Array.from(referenced).sort((a, b) => a.localeCompare(b));
13202
+ }
13203
+ async function inspectProject(config) {
13204
+ const { workspaceRoot, configFile } = config;
13205
+ const parsed = await readJsonFile(join10(workspaceRoot, configFile));
13206
+ if (parsed === void 0) {
13207
+ return void 0;
13208
+ }
13209
+ const projectRoot = dirname2(configFile) === "." ? "" : dirname2(configFile);
13210
+ const nameValue = parsed["name"];
13211
+ const fallbackNameDir = projectRoot === "" ? workspaceRoot : projectRoot;
13212
+ const name = typeof nameValue === "string" && nameValue.length > 0 ? nameValue : basename5(fallbackNameDir);
13213
+ const targets = await resolveTargets({ workspaceRoot, raw: parsed["targets"] });
13214
+ const environmentFiles = await collectEnvironmentFiles({ workspaceRoot, projectRoot });
13215
+ return {
13216
+ name,
13217
+ root: projectRoot,
13218
+ configFile,
13219
+ targets,
13220
+ environmentFiles,
13221
+ referencedEnvironmentFiles: await collectReferencedEnvironmentFiles({ workspaceRoot, targets, environmentFiles })
13222
+ };
13223
+ }
13224
+ async function inspectNxConfig(workspaceRoot) {
13225
+ const parsed = await readJsonFile(join10(workspaceRoot, "nx.json"));
13226
+ const targetDefaultNames = [];
13227
+ const plugins = [];
13228
+ const targetDefaults = parsed?.["targetDefaults"];
13229
+ if (targetDefaults !== null && typeof targetDefaults === "object") {
13230
+ for (const key of Object.keys(targetDefaults)) {
13231
+ if (!key.includes(":")) {
13232
+ targetDefaultNames.push(key);
13233
+ }
13234
+ }
13235
+ }
13236
+ const pluginsValue = parsed?.["plugins"];
13237
+ if (Array.isArray(pluginsValue)) {
13238
+ for (const entry of pluginsValue) {
13239
+ if (typeof entry === "string") {
13240
+ plugins.push(entry);
13241
+ } else if (entry !== null && typeof entry === "object") {
13242
+ const plugin = entry["plugin"];
13243
+ if (typeof plugin === "string") {
13244
+ plugins.push(plugin);
13245
+ }
13246
+ }
13247
+ }
13248
+ }
13249
+ return { targetDefaultNames, plugins };
13250
+ }
13251
+ async function inspectFirebaseConfig(workspaceRoot) {
13252
+ const parsed = await readJsonFile(join10(workspaceRoot, "firebase.json"));
13253
+ if (parsed === void 0) {
13254
+ return { present: false, hostingTargets: [] };
13255
+ }
13256
+ const hostingValue = parsed["hosting"];
13257
+ const singleHostingEntry = hostingValue !== null && typeof hostingValue === "object" ? [hostingValue] : [];
13258
+ const entries = Array.isArray(hostingValue) ? hostingValue : singleHostingEntry;
13259
+ const hostingTargets = [];
13260
+ for (const entry of entries) {
13261
+ if (entry === null || typeof entry !== "object") {
13262
+ continue;
13263
+ }
13264
+ const record = entry;
13265
+ const target = record["target"];
13266
+ const site = record["site"];
13267
+ if (typeof target === "string") {
13268
+ hostingTargets.push(target);
13269
+ } else if (typeof site === "string") {
13270
+ hostingTargets.push(site);
13271
+ }
13272
+ }
13273
+ return { present: true, hostingTargets };
13274
+ }
13275
+ async function collectExtraReferenceSources(workspaceRoot) {
13276
+ const collected = [];
13277
+ for (const candidate of EXTRA_REFERENCE_SOURCES) {
13278
+ const absolute = join10(workspaceRoot, candidate);
13279
+ let stats;
13280
+ try {
13281
+ stats = await stat10(absolute);
13282
+ } catch {
13283
+ continue;
13284
+ }
13285
+ if (stats.isFile()) {
13286
+ collected.push(candidate);
13287
+ } else if (stats.isDirectory()) {
13288
+ const entries = await readdir8(absolute, { withFileTypes: true });
13289
+ for (const entry of entries) {
13290
+ if (entry.isFile() && /\.ya?ml$/.test(entry.name)) {
13291
+ collected.push(join10(candidate, entry.name));
13292
+ }
13293
+ }
13294
+ }
13295
+ }
13296
+ const rootEntries = await readdir8(workspaceRoot, { withFileTypes: true }).catch(() => []);
13297
+ for (const entry of rootEntries) {
13298
+ if (entry.isFile() && entry.name.endsWith(".sh")) {
13299
+ collected.push(entry.name);
13300
+ }
13301
+ }
13302
+ return collected.sort((a, b) => a.localeCompare(b));
13303
+ }
13304
+ async function collectPackageScriptReferences(workspaceRoot) {
13305
+ const parsed = await readJsonFile(join10(workspaceRoot, "package.json"));
13306
+ const scripts = parsed?.["scripts"];
13307
+ const collected = [];
13308
+ if (scripts !== null && typeof scripts === "object") {
13309
+ for (const [scriptName, value] of Object.entries(scripts)) {
13310
+ if (typeof value === "string") {
13311
+ for (const reference of scanTargetReferences({ text: value, sourceFile: "package.json", sourceTarget: scriptName })) {
13312
+ collected.push(reference);
13313
+ }
13314
+ }
13315
+ }
13316
+ }
13317
+ return collected;
13318
+ }
13319
+ async function inspectWorkspace(input) {
13320
+ const workspaceRoot = resolve3(input.workspaceRoot);
13321
+ const configPaths = await collectProjectConfigPaths({ workspaceRoot, relativeDir: "", depth: 0 });
13322
+ const projects = [];
13323
+ const references = [];
13324
+ for (const configFile of [...configPaths].sort((a, b) => a.localeCompare(b))) {
13325
+ const project = await inspectProject({ workspaceRoot, configFile });
13326
+ if (project === void 0) {
13327
+ continue;
13328
+ }
13329
+ projects.push(project);
13330
+ for (const target of project.targets) {
13331
+ for (const command of target.commands) {
13332
+ for (const reference of scanTargetReferences({ text: command, sourceFile: configFile, sourceTarget: target.name })) {
13333
+ references.push(reference);
13334
+ }
13335
+ }
13336
+ }
13337
+ }
13338
+ for (const sourceFile of await collectExtraReferenceSources(workspaceRoot)) {
13339
+ const text = await readFile11(join10(workspaceRoot, sourceFile), "utf8").catch(() => void 0);
13340
+ if (text !== void 0) {
13341
+ for (const reference of scanDocumentTargetReferences({ text, sourceFile })) {
13342
+ references.push(reference);
13343
+ }
13344
+ }
13345
+ }
13346
+ for (const reference of await collectPackageScriptReferences(workspaceRoot)) {
13347
+ references.push(reference);
13348
+ }
13349
+ return {
13350
+ workspaceRoot,
13351
+ projects,
13352
+ references,
13353
+ firebase: await inspectFirebaseConfig(workspaceRoot),
13354
+ nx: await inspectNxConfig(workspaceRoot)
13355
+ };
13356
+ }
13357
+ function pathEscapesWorkspaceRoot(config) {
13358
+ const { workspaceRoot, candidate } = config;
13359
+ const resolved = resolve3(workspaceRoot, candidate);
13360
+ const rel = relative6(workspaceRoot, resolved);
13361
+ return rel.startsWith("..");
13362
+ }
13363
+
13364
+ // packages/dbx-cli/validate/src/lib/workspace-validate/types.ts
13365
+ var WORKSPACE_RULE_GROUPS = ["targets", "deploy"];
13366
+ var DEPLOY_TARGET_PREFIX = "ci-deploy-";
13367
+
13368
+ // packages/dbx-cli/validate/src/lib/workspace-validate/rules.ts
13369
+ 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"]);
13370
+ var ESBUILD_CONFIG_EXECUTORS = /* @__PURE__ */ new Set(["@nx/esbuild:esbuild"]);
13371
+ var PLUGIN_INFERRED_TARGETS = [
13372
+ { plugin: "@nx/eslint/plugin", targets: ["lint"] },
13373
+ { plugin: "@nx/vitest", targets: ["test", "test-ci"] },
13374
+ { plugin: "@nx/vite", targets: ["build", "serve", "preview", "test"] },
13375
+ { plugin: "@nx/jest", targets: ["test"] },
13376
+ { plugin: "@nx/webpack/plugin", targets: ["build", "serve", "preview"] },
13377
+ { plugin: "@nx/rollup/plugin", targets: ["build"] },
13378
+ { plugin: "@nx/js/typescript", targets: ["typecheck", "build"] },
13379
+ { plugin: "@nx/playwright/plugin", targets: ["e2e", "e2e-ci"] },
13380
+ { plugin: "@nx/cypress/plugin", targets: ["e2e", "component-test"] },
13381
+ { plugin: "@nx/next/plugin", targets: ["build", "dev", "start", "serve-static"] }
13382
+ ];
13383
+ var FILE_EXTENSION_PATTERN = /\.(?:ts|tsx|js|mjs|cjs|json|sh|ya?ml|md|html|scss|css)$/;
13384
+ var DEPLOY_LANE_PATTERN = new RegExp(`^${DEPLOY_TARGET_PREFIX}([a-z0-9]+)$`);
13385
+ function pushViolation11(buffer, violation) {
13386
+ const severity = violation.severity ?? "error";
13387
+ const filled = {
13388
+ code: violation.code,
13389
+ severity,
13390
+ message: violation.message,
13391
+ group: violation.group,
13392
+ project: violation.project,
13393
+ file: violation.file,
13394
+ line: violation.line,
13395
+ remediation: attachRemediation(violation.code)
13396
+ };
13397
+ buffer.push(filled);
13398
+ }
13399
+ function looksLikeProjectName(token) {
13400
+ return !token.includes("/") && !token.includes("\\") && !FILE_EXTENSION_PATTERN.test(token);
13401
+ }
13402
+ function isExemptTargetName(config) {
13403
+ const { targetName, nx } = config;
13404
+ let result = nx.targetDefaultNames.includes(targetName);
13405
+ if (!result) {
13406
+ const plugins = new Set(nx.plugins);
13407
+ result = PLUGIN_INFERRED_TARGETS.some((entry) => plugins.has(entry.plugin) && entry.targets.includes(targetName));
13408
+ }
13409
+ return result;
13410
+ }
13411
+ function substituteOutputTokens(config) {
13412
+ const { entry, options, projectRoot } = config;
13413
+ let unresolved = false;
13414
+ const withOptions = entry.replaceAll(/\{options\.(\w+)\}/g, (_match, key) => {
13415
+ const value = options[key];
13416
+ let replacement;
13417
+ if (typeof value === "string") {
13418
+ replacement = value;
13419
+ } else if (typeof value === "number" || typeof value === "boolean") {
13420
+ replacement = String(value);
13421
+ } else {
13422
+ unresolved = true;
13423
+ replacement = "";
13424
+ }
13425
+ return replacement;
13426
+ });
13427
+ const projectRootValue = projectRoot === "" ? "." : projectRoot;
13428
+ return unresolved ? void 0 : withOptions.replaceAll("{workspaceRoot}", ".").replaceAll("{projectRoot}", projectRootValue);
13429
+ }
13430
+ function environmentSelectionState(config) {
13431
+ const { executor, configuration } = config;
13432
+ let result;
13433
+ if (executor !== void 0 && FILE_REPLACEMENT_EXECUTORS.has(executor)) {
13434
+ result = configuration.fileReplacements.length > 0 ? "ok" : "missing";
13435
+ } else if (executor !== void 0 && ESBUILD_CONFIG_EXECUTORS.has(executor)) {
13436
+ result = configuration.esbuildConfig === void 0 ? "missing" : "ok";
13437
+ } else {
13438
+ result = "unknown";
13439
+ }
13440
+ return result;
13441
+ }
13442
+ function environmentSelectionOptionName(executor) {
13443
+ return executor !== void 0 && ESBUILD_CONFIG_EXECUTORS.has(executor) ? "esbuildConfig" : "fileReplacements";
13444
+ }
13445
+ function indexTargets(project) {
13446
+ const map = /* @__PURE__ */ new Map();
13447
+ for (const target of project.targets) {
13448
+ map.set(target.name, target);
13449
+ }
13450
+ return map;
13451
+ }
13452
+ function checkTargetReferences(config) {
13453
+ const { inspection, targetsByProject, violations } = config;
13454
+ const group = "targets";
13455
+ for (const reference of inspection.references) {
13456
+ const targets = targetsByProject.get(reference.project);
13457
+ if (targets === void 0) {
13458
+ if (looksLikeProjectName(reference.project)) {
13459
+ pushViolation11(violations, {
13460
+ code: "WORKSPACE_TARGET_REF_PROJECT_MISSING",
13461
+ message: `\`${reference.raw}\` references project \`${reference.project}\`, which the workspace does not declare.`,
13462
+ group,
13463
+ project: reference.project,
13464
+ file: reference.sourceFile,
13465
+ line: reference.line
13466
+ });
13467
+ }
13468
+ continue;
13469
+ }
13470
+ const target = targets.get(reference.target);
13471
+ if (target === void 0) {
13472
+ if (!isExemptTargetName({ targetName: reference.target, nx: inspection.nx })) {
13473
+ pushViolation11(violations, {
13474
+ code: "WORKSPACE_TARGET_REF_TARGET_MISSING",
13475
+ message: `\`${reference.raw}\` references target \`${reference.target}\` on project \`${reference.project}\`, which declares no such target.`,
13476
+ group,
13477
+ project: reference.project,
13478
+ file: reference.sourceFile,
13479
+ line: reference.line
13480
+ });
13481
+ }
13482
+ continue;
13483
+ }
13484
+ if (reference.configuration !== void 0 && target.configurations.length > 0 && !target.configurations.some((configuration) => configuration.name === reference.configuration)) {
13485
+ const declared = target.configurations.map((configuration) => configuration.name).join(", ");
13486
+ 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\`)`;
13487
+ pushViolation11(violations, {
13488
+ code: "WORKSPACE_TARGET_REF_CONFIGURATION_MISSING",
13489
+ 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}.`,
13490
+ group,
13491
+ project: reference.project,
13492
+ file: reference.sourceFile,
13493
+ line: reference.line
13494
+ });
13495
+ }
13496
+ }
13497
+ }
13498
+ function checkTargetOutputs(config) {
13499
+ const { inspection, violations } = config;
13500
+ const group = "targets";
13501
+ for (const project of inspection.projects) {
13502
+ for (const target of project.targets) {
13503
+ for (const entry of target.outputs) {
13504
+ const substituted = substituteOutputTokens({ entry, options: target.options, projectRoot: project.root });
13505
+ if (substituted === void 0) {
13506
+ continue;
13507
+ }
13508
+ if (pathEscapesWorkspaceRoot({ workspaceRoot: inspection.workspaceRoot, candidate: substituted })) {
13509
+ pushViolation11(violations, {
13510
+ code: "WORKSPACE_TARGET_OUTPUT_ESCAPES_ROOT",
13511
+ 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.`,
13512
+ group,
13513
+ project: project.name,
13514
+ file: project.configFile,
13515
+ line: void 0
13516
+ });
13517
+ }
13518
+ }
13519
+ }
13520
+ }
13521
+ }
13522
+ function checkDeployLaneEnvironmentSelection(config) {
13523
+ const { inspection, targetsByProject, violations } = config;
13524
+ const group = "deploy";
13525
+ for (const project of inspection.projects) {
13526
+ const targets = targetsByProject.get(project.name);
13527
+ if (targets === void 0) {
13528
+ continue;
13529
+ }
13530
+ const deployTargetNames = new Set(project.targets.filter((target) => target.name.startsWith(DEPLOY_TARGET_PREFIX)).map((target) => target.name));
13531
+ for (const reference of inspection.references) {
13532
+ if (reference.sourceFile !== project.configFile || reference.sourceTarget === void 0 || !deployTargetNames.has(reference.sourceTarget) || reference.project !== project.name || reference.configuration === void 0) {
13533
+ continue;
13534
+ }
13535
+ const buildTarget = targets.get(reference.target);
13536
+ const configuration = buildTarget?.configurations.find((entry) => entry.name === reference.configuration);
13537
+ if (buildTarget === void 0 || configuration === void 0) {
13538
+ continue;
13539
+ }
13540
+ if (environmentSelectionState({ executor: buildTarget.executor, configuration }) === "missing") {
13541
+ pushViolation11(violations, {
13542
+ code: "WORKSPACE_DEPLOY_LANE_NO_ENVIRONMENT_SELECTION",
13543
+ 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\`.`,
13544
+ group,
13545
+ project: project.name,
13546
+ file: project.configFile,
13547
+ line: void 0
13548
+ });
13549
+ }
13550
+ }
13551
+ }
13552
+ }
13553
+ function checkConfigurationPaths(config) {
13554
+ const { inspection, violations } = config;
13555
+ const group = "deploy";
13556
+ for (const project of inspection.projects) {
13557
+ for (const target of project.targets) {
13558
+ for (const configuration of target.configurations) {
13559
+ for (const replacement of configuration.fileReplacements) {
13560
+ if (!replacement.replaceExists) {
13561
+ pushViolation11(violations, {
13562
+ code: "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_MISSING",
13563
+ message: `\`${project.name}:${target.name}:${configuration.name}\` replaces \`${replacement.replace}\`, which does not exist.`,
13564
+ group,
13565
+ project: project.name,
13566
+ file: project.configFile,
13567
+ line: void 0
13568
+ });
13569
+ }
13570
+ if (!replacement.withExists) {
13571
+ pushViolation11(violations, {
13572
+ code: "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_MISSING",
13573
+ message: `\`${project.name}:${target.name}:${configuration.name}\` replaces with \`${replacement.with}\`, which does not exist.`,
13574
+ group,
13575
+ project: project.name,
13576
+ file: project.configFile,
13577
+ line: void 0
13578
+ });
13579
+ }
13580
+ }
13581
+ if (configuration.esbuildConfig !== void 0 && !configuration.esbuildConfigExists) {
13582
+ pushViolation11(violations, {
13583
+ code: "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_MISSING",
13584
+ message: `\`${project.name}:${target.name}:${configuration.name}\` sets \`esbuildConfig\` to \`${configuration.esbuildConfig}\`, which does not exist.`,
13585
+ group,
13586
+ project: project.name,
13587
+ file: project.configFile,
13588
+ line: void 0
13589
+ });
13590
+ }
13591
+ }
13592
+ }
13593
+ }
13594
+ }
13595
+ function checkUnreferencedEnvironmentFiles(config) {
13596
+ const { inspection, violations } = config;
13597
+ const group = "deploy";
13598
+ for (const project of inspection.projects) {
13599
+ const referenced = new Set(project.referencedEnvironmentFiles);
13600
+ for (const environmentFile of project.environmentFiles) {
13601
+ if (!referenced.has(environmentFile)) {
13602
+ pushViolation11(violations, {
13603
+ code: "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_UNREFERENCED",
13604
+ message: `\`${environmentFile}\` is not selected by any build configuration of \`${project.name}\` \u2014 it is edited and reviewed but never compiled.`,
13605
+ severity: "warning",
13606
+ group,
13607
+ project: project.name,
13608
+ file: project.configFile,
13609
+ line: void 0
13610
+ });
13611
+ }
13612
+ }
13613
+ }
13614
+ }
13615
+ function checkHostingTargets(config) {
13616
+ const { inspection, violations } = config;
13617
+ const group = "deploy";
13618
+ if (!inspection.firebase.present) {
13619
+ return;
13620
+ }
13621
+ const declared = new Set(inspection.firebase.hostingTargets);
13622
+ for (const project of inspection.projects) {
13623
+ for (const target of project.targets) {
13624
+ for (const command of target.commands) {
13625
+ for (const hostingTarget of scanHostingTargets(command)) {
13626
+ if (!declared.has(hostingTarget)) {
13627
+ pushViolation11(violations, {
13628
+ code: "WORKSPACE_DEPLOY_HOSTING_TARGET_MISSING",
13629
+ message: `\`${project.name}:${target.name}\` deploys \`hosting:${hostingTarget}\`, which \`firebase.json\` does not declare (declared: [${Array.from(declared).join(", ") || "none"}]).`,
13630
+ group,
13631
+ project: project.name,
13632
+ file: project.configFile,
13633
+ line: void 0
13634
+ });
13635
+ }
13636
+ }
13637
+ }
13638
+ }
13639
+ }
13640
+ }
13641
+ function checkDeployLaneSymmetry(config) {
13642
+ const { inspection, violations } = config;
13643
+ const group = "deploy";
13644
+ const lanesByProject = /* @__PURE__ */ new Map();
13645
+ const allLanes = /* @__PURE__ */ new Set();
13646
+ for (const project of inspection.projects) {
13647
+ const lanes = /* @__PURE__ */ new Set();
13648
+ for (const target of project.targets) {
13649
+ const match = DEPLOY_LANE_PATTERN.exec(target.name);
13650
+ if (match !== null) {
13651
+ lanes.add(match[1]);
13652
+ allLanes.add(match[1]);
13653
+ }
13654
+ }
13655
+ if (lanes.size > 0) {
13656
+ lanesByProject.set(project.name, lanes);
13657
+ }
13658
+ }
13659
+ if (lanesByProject.size < 2) {
13660
+ return;
13661
+ }
13662
+ for (const project of inspection.projects) {
13663
+ const lanes = lanesByProject.get(project.name);
13664
+ if (lanes === void 0) {
13665
+ continue;
13666
+ }
13667
+ for (const lane of Array.from(allLanes).sort((a, b) => a.localeCompare(b))) {
13668
+ if (!lanes.has(lane)) {
13669
+ pushViolation11(violations, {
13670
+ code: "WORKSPACE_DEPLOY_LANE_ASYMMETRIC",
13671
+ 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}\`.`,
13672
+ severity: "warning",
13673
+ group,
13674
+ project: project.name,
13675
+ file: project.configFile,
13676
+ line: void 0
13677
+ });
13678
+ }
13679
+ }
13680
+ }
13681
+ }
13682
+ function runRules9(input) {
13683
+ const { inspection, groups } = input;
13684
+ const violations = [];
13685
+ const targetsByProject = /* @__PURE__ */ new Map();
13686
+ for (const project of inspection.projects) {
13687
+ targetsByProject.set(project.name, indexTargets(project));
13688
+ }
13689
+ if (groups.includes("targets")) {
13690
+ checkTargetReferences({ inspection, targetsByProject, violations });
13691
+ checkTargetOutputs({ inspection, violations });
13692
+ }
13693
+ if (groups.includes("deploy")) {
13694
+ checkDeployLaneEnvironmentSelection({ inspection, targetsByProject, violations });
13695
+ checkConfigurationPaths({ inspection, violations });
13696
+ checkUnreferencedEnvironmentFiles({ inspection, violations });
13697
+ checkHostingTargets({ inspection, violations });
13698
+ checkDeployLaneSymmetry({ inspection, violations });
13699
+ }
13700
+ return violations;
13701
+ }
13702
+
13703
+ // packages/dbx-cli/validate/src/lib/workspace-validate/validate.ts
13704
+ function validateWorkspace(input) {
13705
+ const { inspection } = input;
13706
+ const groups = input.groups === void 0 || input.groups.length === 0 ? WORKSPACE_RULE_GROUPS : input.groups;
13707
+ const violations = runRules9({ inspection, groups });
13708
+ let errorCount = 0;
13709
+ let warningCount = 0;
13710
+ for (const violation of violations) {
13711
+ if (violation.severity === "error") {
13712
+ errorCount += 1;
13713
+ } else {
13714
+ warningCount += 1;
13715
+ }
13716
+ }
13717
+ return {
13718
+ violations,
13719
+ errorCount,
13720
+ warningCount,
13721
+ projectsChecked: inspection.projects.length,
13722
+ referencesChecked: inspection.references.length,
13723
+ groups
13724
+ };
13725
+ }
13726
+
12737
13727
  // packages/dbx-cli/validate/src/lib/model-list-component/index.ts
12738
13728
  var model_list_component_exports = {};
12739
13729
  __export(model_list_component_exports, {
@@ -12744,8 +13734,8 @@ __export(model_list_component_exports, {
12744
13734
  });
12745
13735
 
12746
13736
  // packages/dbx-cli/model-test/src/lib/fixture/inspect.ts
12747
- import { readFile as readFile11 } from "node:fs/promises";
12748
- import { join as join10 } from "node:path";
13737
+ import { readFile as readFile12 } from "node:fs/promises";
13738
+ import { join as join11 } from "node:path";
12749
13739
 
12750
13740
  // packages/dbx-cli/model-test/src/lib/fixture/extract.ts
12751
13741
  import { Node as Node16, Project as Project9, SyntaxKind as SyntaxKind7 } from "ts-morph";
@@ -13242,21 +14232,21 @@ function collectIdentityImports(sourceFile) {
13242
14232
  // packages/dbx-cli/model-test/src/lib/fixture/inspect.ts
13243
14233
  var FIXTURE_RELATIVE_PATH = "src/test/fixture.ts";
13244
14234
  async function inspectAppFixtures(apiAbs, apiRel) {
13245
- const absolutePath = join10(apiAbs, FIXTURE_RELATIVE_PATH);
14235
+ const absolutePath = join11(apiAbs, FIXTURE_RELATIVE_PATH);
13246
14236
  const relativePath = `${apiRel}/${FIXTURE_RELATIVE_PATH}`;
13247
- const text = await readFile11(absolutePath, "utf8");
14237
+ const text = await readFile12(absolutePath, "utf8");
13248
14238
  return extractAppFixturesFromText({ text, fixturePath: relativePath });
13249
14239
  }
13250
14240
 
13251
14241
  // packages/dbx-cli/validate/src/lib/model-list-component/extract.ts
13252
- import { readdir as readdir8, readFile as readFile12, stat as stat10 } from "node:fs/promises";
13253
- import { basename as basename5, join as join11, relative as relative6, sep as sep7 } from "node:path";
14242
+ import { readdir as readdir9, readFile as readFile13, stat as stat11 } from "node:fs/promises";
14243
+ import { basename as basename6, join as join12, relative as relative7, sep as sep7 } from "node:path";
13254
14244
  import { Node as Node17, Project as Project10 } from "ts-morph";
13255
14245
  var MODEL_SUBPATH = "src/lib/model";
13256
14246
  var MODEL_SUBPATH_PREFIX = `${MODEL_SUBPATH}/`;
13257
14247
  var RESERVED_NAMES = RESERVED_MODEL_FOLDERS.map((r) => r.name);
13258
14248
  async function extractComponentModels(componentAbs) {
13259
- const modelRoot = join11(componentAbs, MODEL_SUBPATH);
14249
+ const modelRoot = join12(componentAbs, MODEL_SUBPATH);
13260
14250
  const folders = await listFolders(modelRoot);
13261
14251
  if (folders === void 0) {
13262
14252
  const result2 = { modelRoot, models: [], skipped: [], unidentifiedFolders: [] };
@@ -13346,13 +14336,13 @@ function projectRich(model, folder) {
13346
14336
  }
13347
14337
  async function fallbackForFolder(input) {
13348
14338
  const { modelRoot, componentAbs, folder, project } = input;
13349
- const folderAbs = join11(modelRoot, folder);
14339
+ const folderAbs = join12(modelRoot, folder);
13350
14340
  const mainFile = await readMainFile(folderAbs, folder);
13351
14341
  if (mainFile === void 0) return void 0;
13352
14342
  const sourceFile = project.createSourceFile(mainFile.fileName, mainFile.text, { overwrite: true });
13353
14343
  const identity = findIdentityIn(sourceFile);
13354
14344
  if (!identity) return void 0;
13355
- const sourceFileRel = relative6(componentAbs, mainFile.absPath).split(sep7).join("/");
14345
+ const sourceFileRel = relative7(componentAbs, mainFile.absPath).split(sep7).join("/");
13356
14346
  return {
13357
14347
  folder,
13358
14348
  modelName: deriveModelName(identity.constName, folder),
@@ -13368,13 +14358,13 @@ async function listFolders(modelRoot) {
13368
14358
  let result;
13369
14359
  let isDir = false;
13370
14360
  try {
13371
- const stats = await stat10(modelRoot);
14361
+ const stats = await stat11(modelRoot);
13372
14362
  isDir = stats.isDirectory();
13373
14363
  } catch {
13374
14364
  isDir = false;
13375
14365
  }
13376
14366
  if (isDir) {
13377
- const entries = await readdir8(modelRoot, { withFileTypes: true });
14367
+ const entries = await readdir9(modelRoot, { withFileTypes: true });
13378
14368
  const out = [];
13379
14369
  for (const entry of entries) {
13380
14370
  if (entry.isDirectory()) out.push(entry.name);
@@ -13385,11 +14375,11 @@ async function listFolders(modelRoot) {
13385
14375
  return result;
13386
14376
  }
13387
14377
  async function readMainFile(folderAbs, folder) {
13388
- const candidate = join11(folderAbs, `${folder}.ts`);
14378
+ const candidate = join12(folderAbs, `${folder}.ts`);
13389
14379
  let result;
13390
14380
  try {
13391
- const text = await readFile12(candidate, "utf8");
13392
- result = { absPath: candidate, fileName: basename5(candidate), text };
14381
+ const text = await readFile13(candidate, "utf8");
14382
+ result = { absPath: candidate, fileName: basename6(candidate), text };
13393
14383
  } catch {
13394
14384
  result = void 0;
13395
14385
  }
@@ -13534,21 +14524,21 @@ __export(model_api_list_app_exports, {
13534
14524
  });
13535
14525
 
13536
14526
  // packages/dbx-cli/validate/src/lib/model-api-list-app/extract.ts
13537
- import { readFile as readFile13 } from "node:fs/promises";
13538
- import { join as join12, relative as relative7, sep as sep8 } from "node:path";
14527
+ import { readFile as readFile14 } from "node:fs/promises";
14528
+ import { join as join13, relative as relative8, sep as sep8 } from "node:path";
13539
14529
  var API_SUFFIX2 = ".api.ts";
13540
14530
  var MODEL_SUBPATH2 = "src/lib";
13541
14531
  async function extractApiList(input) {
13542
- const modelRoot = join12(input.componentAbs, MODEL_SUBPATH2);
14532
+ const modelRoot = join13(input.componentAbs, MODEL_SUBPATH2);
13543
14533
  const apiFiles = await collectFilesWithSuffix(modelRoot, API_SUFFIX2);
13544
14534
  const entries = [];
13545
14535
  const files = [];
13546
14536
  for (const filePath of apiFiles) {
13547
- const text = await readFile13(filePath, "utf8");
14537
+ const text = await readFile14(filePath, "utf8");
13548
14538
  if (!text.includes("callModelFirebaseFunctionMapFactory")) {
13549
14539
  continue;
13550
14540
  }
13551
- const sourceFileRel = relative7(input.componentAbs, filePath).split(sep8).join("/");
14541
+ const sourceFileRel = relative8(input.componentAbs, filePath).split(sep8).join("/");
13552
14542
  const extraction = extractCrudEntries({ name: sourceFileRel, text });
13553
14543
  const fileEntries = extraction.entries.map((entry) => ({ ...entry, sourceFile: sourceFileRel }));
13554
14544
  const counts = countVerbs(fileEntries);
@@ -13679,17 +14669,17 @@ __export(model_api_lookup_exports, {
13679
14669
  });
13680
14670
 
13681
14671
  // packages/dbx-cli/validate/src/lib/model-api-lookup/extract.ts
13682
- import { readFile as readFile15 } from "node:fs/promises";
13683
- import { join as join14, relative as relative9, sep as sep10 } from "node:path";
14672
+ import { readFile as readFile16 } from "node:fs/promises";
14673
+ import { join as join15, relative as relative10, sep as sep10 } from "node:path";
13684
14674
 
13685
14675
  // packages/dbx-cli/validate/src/lib/model-api-lookup/extract-actions.ts
13686
- import { readFile as readFile14 } from "node:fs/promises";
13687
- import { join as join13, relative as relative8, sep as sep9 } from "node:path";
14676
+ import { readFile as readFile15 } from "node:fs/promises";
14677
+ import { join as join14, relative as relative9, sep as sep9 } from "node:path";
13688
14678
  import { Project as Project11, SyntaxKind as SyntaxKind8 } from "ts-morph";
13689
14679
  var ACTION_SERVER_SUFFIX = ".action.server.ts";
13690
14680
  var COMMON_MODEL_SUBPATH = "src/app/common/model";
13691
14681
  async function buildActionLookup(apiAbs) {
13692
- const modelRoot = join13(apiAbs, COMMON_MODEL_SUBPATH);
14682
+ const modelRoot = join14(apiAbs, COMMON_MODEL_SUBPATH);
13693
14683
  const files = await collectFilesWithSuffix(modelRoot, ACTION_SERVER_SUFFIX);
13694
14684
  const methodsByParams = /* @__PURE__ */ new Map();
13695
14685
  const factoriesByParams = /* @__PURE__ */ new Map();
@@ -13698,9 +14688,9 @@ async function buildActionLookup(apiAbs) {
13698
14688
  }
13699
14689
  const project = new Project11({ useInMemoryFileSystem: true, skipAddingFilesFromTsConfig: true });
13700
14690
  for (const filePath of files) {
13701
- const text = await readFile14(filePath, "utf8");
14691
+ const text = await readFile15(filePath, "utf8");
13702
14692
  const sourceFile = project.createSourceFile(filePath, text, { overwrite: true });
13703
- const sourceFileRel = relative8(apiAbs, filePath).split(sep9).join("/");
14693
+ const sourceFileRel = relative9(apiAbs, filePath).split(sep9).join("/");
13704
14694
  collectAbstractMethods({ sourceFile, sourceFileRel, methodsByParams });
13705
14695
  collectFactoryFunctions({ sourceFile, sourceFileRel, factoriesByParams });
13706
14696
  }
@@ -13860,13 +14850,13 @@ function mapDocFields(fields) {
13860
14850
  return result;
13861
14851
  }
13862
14852
  async function collectApiSources(componentAbs) {
13863
- const root = join14(componentAbs, COMPONENT_LIB_SUBPATH2);
14853
+ const root = join15(componentAbs, COMPONENT_LIB_SUBPATH2);
13864
14854
  const files = await collectFilesWithSuffix(root, API_SUFFIX3);
13865
14855
  const sources = [];
13866
14856
  for (const fileAbs of files) {
13867
- const text = await readFile15(fileAbs, "utf8");
14857
+ const text = await readFile16(fileAbs, "utf8");
13868
14858
  if (!text.includes("callModelFirebaseFunctionMapFactory")) continue;
13869
- const fileRel = relative9(componentAbs, fileAbs).split(sep10).join("/");
14859
+ const fileRel = relative10(componentAbs, fileAbs).split(sep10).join("/");
13870
14860
  sources.push({ fileAbs, fileRel, text });
13871
14861
  }
13872
14862
  return sources;
@@ -14014,14 +15004,14 @@ __export(server_actions_list_app_exports, {
14014
15004
  });
14015
15005
 
14016
15006
  // packages/dbx-cli/validate/src/lib/server-actions-list-app/extract.ts
14017
- import { readdir as readdir9, readFile as readFile16 } from "node:fs/promises";
14018
- import { dirname as dirname2, join as join15, relative as relative10, sep as sep11 } from "node:path";
15007
+ import { readdir as readdir10, readFile as readFile17 } from "node:fs/promises";
15008
+ import { dirname as dirname3, join as join16, relative as relative11, sep as sep11 } from "node:path";
14019
15009
  import { Node as Node18, Project as Project12 } from "ts-morph";
14020
15010
  var ACTION_SERVER_SUFFIX2 = ".action.server.ts";
14021
15011
  var COMMON_MODEL_SUBPATH2 = "src/app/common/model";
14022
15012
  var COMMON_BARREL_REL = "src/app/common/index.ts";
14023
15013
  async function extractServerActions(apiAbs, apiRel) {
14024
- const modelRoot = join15(apiAbs, COMMON_MODEL_SUBPATH2);
15014
+ const modelRoot = join16(apiAbs, COMMON_MODEL_SUBPATH2);
14025
15015
  const actionFiles = await collectFilesWithSuffix(modelRoot, ACTION_SERVER_SUFFIX2);
14026
15016
  if (actionFiles.length === 0) {
14027
15017
  return { modelRoot, entries: [], fixtureStatus: "ok" };
@@ -14041,14 +15031,14 @@ async function extractServerActions(apiAbs, apiRel) {
14041
15031
  }
14042
15032
  const entries = [];
14043
15033
  for (const filePath of actionFiles) {
14044
- const text = await readFile16(filePath, "utf8");
15034
+ const text = await readFile17(filePath, "utf8");
14045
15035
  const sourceFile = project.createSourceFile(filePath, text, { overwrite: true });
14046
15036
  const classes = findServerActionClasses(sourceFile);
14047
15037
  for (const className of classes) {
14048
15038
  const wiring = await inspectSiblingModule(filePath, className);
14049
15039
  const expectedGetterName = camelize(className);
14050
15040
  const fixtureCoverage = fixtureStatus === "ok" ? fixtureCoverageMap.get(expectedGetterName) ?? unmatchedFixtureCoverage(expectedGetterName) : void 0;
14051
- const sourceFileRel = relative10(apiAbs, filePath).split(sep11).join("/");
15041
+ const sourceFileRel = relative11(apiAbs, filePath).split(sep11).join("/");
14052
15042
  entries.push({
14053
15043
  className,
14054
15044
  sourceFile: sourceFileRel,
@@ -14090,14 +15080,14 @@ async function inspectSiblingModule(actionFilePath, className) {
14090
15080
  return { modulePath: moduleCandidates[0], providedByModule: false, exportedByModule: false };
14091
15081
  }
14092
15082
  async function findModuleCandidates(actionFilePath) {
14093
- const dir = dirname2(actionFilePath);
15083
+ const dir = dirname3(actionFilePath);
14094
15084
  const out = [];
14095
15085
  try {
14096
- const entries = await readdir9(dir, { withFileTypes: true });
15086
+ const entries = await readdir10(dir, { withFileTypes: true });
14097
15087
  for (const entry of entries) {
14098
15088
  if (!entry.isFile()) continue;
14099
15089
  if (!entry.name.endsWith(".module.ts")) continue;
14100
- out.push(join15(dir, entry.name));
15090
+ out.push(join16(dir, entry.name));
14101
15091
  }
14102
15092
  } catch {
14103
15093
  }
@@ -14107,7 +15097,7 @@ async function findModuleCandidates(actionFilePath) {
14107
15097
  async function safeRead(path) {
14108
15098
  let result;
14109
15099
  try {
14110
- result = await readFile16(path, "utf8");
15100
+ result = await readFile17(path, "utf8");
14111
15101
  } catch {
14112
15102
  result = void 0;
14113
15103
  }
@@ -14154,7 +15144,7 @@ function moduleArrayContains(decoratorArg, propertyName, className) {
14154
15144
  return false;
14155
15145
  }
14156
15146
  async function readCommonBarrelExports(apiAbs) {
14157
- const path = join15(apiAbs, COMMON_BARREL_REL);
15147
+ const path = join16(apiAbs, COMMON_BARREL_REL);
14158
15148
  const text = await safeRead(path);
14159
15149
  if (text === void 0) return /* @__PURE__ */ new Set();
14160
15150
  const project = new Project12({ useInMemoryFileSystem: true });
@@ -14333,8 +15323,8 @@ function formatReportAsJson4(report) {
14333
15323
 
14334
15324
  // packages/dbx-cli/validate/src/lib/dbx-asset-list-app/format.markdown.ts
14335
15325
  function formatReportAsMarkdown4(report) {
14336
- const basename6 = report.componentDir.split("/").pop() ?? report.componentDir;
14337
- 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})`];
15326
+ const basename7 = report.componentDir.split("/").pop() ?? report.componentDir;
15327
+ const lines = [`# App assets \u2014 ${basename7}`, "", `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})`];
14338
15328
  if (report.assets.length === 0) {
14339
15329
  lines.push("", "_None found._");
14340
15330
  } else {
@@ -14522,11 +15512,11 @@ function formatReportAsJson5(report) {
14522
15512
 
14523
15513
  // packages/dbx-cli/validate/src/lib/notification-m-list-app/format.markdown.ts
14524
15514
  function formatReportAsMarkdown5(report) {
14525
- const basename6 = report.componentDir.split("/").pop() ?? report.componentDir;
15515
+ const basename7 = report.componentDir.split("/").pop() ?? report.componentDir;
14526
15516
  const aggregatorRecordText = report.aggregatorRecordName ? code(report.aggregatorRecordName) : "_Not defined._";
14527
15517
  const templateConfigsFactoryText = report.templateConfigsArrayFactoryName ? code(report.templateConfigsArrayFactoryName) : "_Not defined._";
14528
15518
  const lines = [
14529
- `# App notifications \u2014 ${basename6}`,
15519
+ `# App notifications \u2014 ${basename7}`,
14530
15520
  "",
14531
15521
  `Component: \`${report.componentDir}\``,
14532
15522
  `API: \`${report.apiDir}\``,
@@ -14723,9 +15713,9 @@ function formatReportAsJson6(report) {
14723
15713
 
14724
15714
  // packages/dbx-cli/validate/src/lib/storagefile-m-list-app/format.markdown.ts
14725
15715
  function formatReportAsMarkdown6(report) {
14726
- const basename6 = report.componentDir.split("/").pop() ?? report.componentDir;
15716
+ const basename7 = report.componentDir.split("/").pop() ?? report.componentDir;
14727
15717
  const uploadServiceFactoryText = report.uploadServiceFactoryName ? code2(report.uploadServiceFactoryName) : "_Not defined._";
14728
- 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})`];
15718
+ const lines = [`# App storagefiles \u2014 ${basename7}`, "", `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})`];
14729
15719
  if (report.purposes.length === 0) {
14730
15720
  lines.push("", "_None found._");
14731
15721
  } else {
@@ -14909,8 +15899,8 @@ __export(dbx_color_template_list_app_exports, {
14909
15899
  });
14910
15900
 
14911
15901
  // packages/dbx-cli/validate/src/lib/dbx-color-template-list-app/inspect.ts
14912
- import { readFile as readFile17 } from "node:fs/promises";
14913
- import { join as join16, relative as relative11, sep as sep12 } from "node:path";
15902
+ import { readFile as readFile18 } from "node:fs/promises";
15903
+ import { join as join17, relative as relative12, sep as sep12 } from "node:path";
14914
15904
  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"];
14915
15905
  async function inspectColorTemplates(apiAbs, apiRel) {
14916
15906
  const present = [];
@@ -14920,7 +15910,7 @@ async function inspectColorTemplates(apiAbs, apiRel) {
14920
15910
  for (const candidate of candidates) {
14921
15911
  const text = await readFileIfExists2(candidate);
14922
15912
  if (text === void 0) continue;
14923
- const rel = relative11(apiAbs, candidate).split(sep12).join("/");
15913
+ const rel = relative12(apiAbs, candidate).split(sep12).join("/");
14924
15914
  present.push({ relPath: rel, text });
14925
15915
  }
14926
15916
  } catch (err) {
@@ -14937,25 +15927,25 @@ async function inspectColorTemplates(apiAbs, apiRel) {
14937
15927
  async function collectCandidatePaths(apiAbs) {
14938
15928
  const explicit = [];
14939
15929
  for (const rel of CANDIDATE_RELPATHS) {
14940
- explicit.push(join16(apiAbs, rel));
15930
+ explicit.push(join17(apiAbs, rel));
14941
15931
  }
14942
- const scanned = await scanProviderFiles(join16(apiAbs, "src"));
15932
+ const scanned = await scanProviderFiles(join17(apiAbs, "src"));
14943
15933
  return [...explicit, ...scanned];
14944
15934
  }
14945
15935
  async function scanProviderFiles(srcAbs) {
14946
- const { readdir: readdir10 } = await import("node:fs/promises");
15936
+ const { readdir: readdir11 } = await import("node:fs/promises");
14947
15937
  const out = [];
14948
15938
  const stack = [srcAbs];
14949
15939
  while (stack.length > 0) {
14950
15940
  const current = stack.pop();
14951
15941
  let entries;
14952
15942
  try {
14953
- entries = await readdir10(current, { withFileTypes: true });
15943
+ entries = await readdir11(current, { withFileTypes: true });
14954
15944
  } catch {
14955
15945
  continue;
14956
15946
  }
14957
15947
  for (const entry of entries) {
14958
- const full = join16(current, entry.name);
15948
+ const full = join17(current, entry.name);
14959
15949
  if (entry.isDirectory()) {
14960
15950
  stack.push(full);
14961
15951
  continue;
@@ -14970,7 +15960,7 @@ async function scanProviderFiles(srcAbs) {
14970
15960
  async function readFileIfExists2(absPath) {
14971
15961
  let result;
14972
15962
  try {
14973
- result = await readFile17(absPath, "utf8");
15963
+ result = await readFile18(absPath, "utf8");
14974
15964
  } catch (err) {
14975
15965
  const code3 = err.code;
14976
15966
  if (code3 !== "ENOENT" && code3 !== "ENOTDIR" && code3 !== "EISDIR") {
@@ -15674,11 +16664,12 @@ function matchKindForRadius(exact, raw) {
15674
16664
  if (exact.defaults.light === raw || exact.defaults.dark === raw) return "(exact-match design-system corner token).";
15675
16665
  return "(idiomatic full / pill radius \u2014 design system token).";
15676
16666
  }
15677
- function findExactRoleToken(registry, raw, role) {
16667
+ function findExactRoleToken(params) {
16668
+ const { registry, raw, role, isAllowed } = params;
15678
16669
  const candidates = registry.byRole.get(role) ?? [];
15679
16670
  let result;
15680
16671
  for (const entry of candidates) {
15681
- if (entry.defaults.light === raw || entry.defaults.dark === raw) {
16672
+ if ((entry.defaults.light === raw || entry.defaults.dark === raw) && (isAllowed === void 0 || isAllowed(entry))) {
15682
16673
  result = entry;
15683
16674
  break;
15684
16675
  }
@@ -15823,7 +16814,7 @@ var hardcodedRadius = (input) => {
15823
16814
  const colonIndex = input.scss.indexOf(":", match.index);
15824
16815
  if (colonIndex !== -1 && isInsideScssVarDeclaration(input.scss, colonIndex)) continue;
15825
16816
  const raw = match[1];
15826
- let exact = findExactRoleToken(input.tokenRegistry, raw, "radius");
16817
+ let exact = findExactRoleToken({ registry: input.tokenRegistry, raw, role: "radius" });
15827
16818
  if (exact === void 0 && FULL_RADIUS_SYNONYMS.has(raw)) {
15828
16819
  exact = input.tokenRegistry.findByCssVariable("--mat-sys-corner-full");
15829
16820
  }
@@ -15854,7 +16845,7 @@ var hardcodedShadow = (input) => {
15854
16845
  if (match.index === void 0) continue;
15855
16846
  const raw = match[1].trim();
15856
16847
  if (raw.startsWith("var(") || raw === "none") continue;
15857
- const exact = findExactRoleToken(input.tokenRegistry, raw, "elevation");
16848
+ const exact = findExactRoleToken({ registry: input.tokenRegistry, raw, role: "elevation" });
15858
16849
  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.`;
15859
16850
  matches.push({
15860
16851
  id: "hardcoded-shadow",
@@ -15899,12 +16890,13 @@ var hardcodedHintColor = (input) => {
15899
16890
  return matches;
15900
16891
  };
15901
16892
  var HARDCODED_PADDING_RE = /(?:padding|margin)\s*(?:-(?:top|right|bottom|left))?\s*:\s*(\d+(?:\.\d+)?px)(?!\s*\))/gi;
16893
+ var DBX_PADDING_SCALE_RE = /^--dbx-padding-\d+$/;
15902
16894
  var hardcodedPadding = (input) => {
15903
16895
  const matches = [];
15904
16896
  for (const match of input.scss.matchAll(HARDCODED_PADDING_RE)) {
15905
16897
  if (match.index === void 0) continue;
15906
16898
  const raw = match[1];
15907
- const exact = findExactRoleToken(input.tokenRegistry, raw, "spacing");
16899
+ const exact = findExactRoleToken({ registry: input.tokenRegistry, raw, role: "spacing", isAllowed: (entry) => DBX_PADDING_SCALE_RE.test(entry.cssVariable) });
15908
16900
  if (exact !== void 0) {
15909
16901
  const utility = exact.utilityClasses?.[0];
15910
16902
  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");
@@ -16642,5 +17634,6 @@ export {
16642
17634
  storagefile_m_validate_folder_exports as storagefileValidateFolder,
16643
17635
  system_m_list_app_exports as systemListApp,
16644
17636
  system_m_validate_folder_exports as systemValidateFolder,
16645
- ui_smell_check_exports as uiSmellCheck
17637
+ ui_smell_check_exports as uiSmellCheck,
17638
+ workspace_validate_exports as workspaceValidate
16646
17639
  };