(path: string | undefined, options: LintOptions)
| 167 | } |
| 168 | |
| 169 | async function lintFile(path: string | undefined, options: LintOptions): Promise<LintFileResult> { |
| 170 | // Loading `.env` and injecting integration env vars mutates `process.env`; restore it afterwards |
| 171 | // so a lint run is idempotent and doesn't clobber caller-provided env values (see withRestoredEnv). |
| 172 | return withRestoredEnv(async () => { |
| 173 | const { absolutePath } = await resolvePathToDeepnoteFile(path) |
| 174 | const fileDir = dirname(absolutePath) |
| 175 | |
| 176 | const { file: deepnoteFile, warnings } = await loadAndResolveDeepnoteFile(absolutePath) |
| 177 | emitInitResolverWarnings(warnings, options.output === 'json') |
| 178 | |
| 179 | // Load .env file so env: references in integrations can be resolved |
| 180 | dotenv.config({ path: join(fileDir, DEFAULT_ENV_FILE), quiet: true }) |
| 181 | |
| 182 | // Load and parse integrations file |
| 183 | const integrationsFilePath = options.integrationsFile ?? getDefaultIntegrationsFilePath(fileDir) |
| 184 | |
| 185 | // If the user explicitly specified an integrations file, fail loudly when it's missing |
| 186 | // (an absent implicit default file is fine — integrations are optional in that case). |
| 187 | if (options.integrationsFile) { |
| 188 | try { |
| 189 | await fs.stat(integrationsFilePath) |
| 190 | } catch (error) { |
| 191 | if (isErrnoENOENT(error)) { |
| 192 | throw new FileResolutionError(`File not found: ${integrationsFilePath}`) |
| 193 | } |
| 194 | throw error |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | debug(`Loading integrations from: ${integrationsFilePath}`) |
| 199 | const parsedIntegrations = await parseIntegrationsFile(integrationsFilePath) |
| 200 | |
| 201 | debug(`Parsed ${parsedIntegrations.integrations.length} integrations, ${parsedIntegrations.issues.length} issues`) |
| 202 | |
| 203 | // Generate integration env vars and inject them into process.env so checkMissingIntegrations |
| 204 | // works correctly. Generation failures (e.g. invalid BigQuery/Spanner service_account JSON) are |
| 205 | // surfaced as configuration issues so a schema-valid-but-impossible to generate integration doesn't lint green. |
| 206 | const integrationsIssues = [...parsedIntegrations.issues] |
| 207 | if (parsedIntegrations.integrations.length > 0) { |
| 208 | const { envVars, issues } = generateIntegrationEnvVars(parsedIntegrations.integrations, fileDir) |
| 209 | integrationsIssues.push(...issues) |
| 210 | for (const { name, value } of envVars) { |
| 211 | process.env[name] = value |
| 212 | } |
| 213 | debug( |
| 214 | `Injected ${envVars.length} environment variables for ${parsedIntegrations.integrations.length} integrations` |
| 215 | ) |
| 216 | } |
| 217 | |
| 218 | debug(`Analyzing blocks...`) |
| 219 | const pythonInterpreter = options.python ? await resolvePythonExecutable(options.python) : undefined |
| 220 | const { lint } = await checkForIssues(deepnoteFile, { |
| 221 | notebook: options.notebook, |
| 222 | pythonInterpreter, |
| 223 | }) |
| 224 | |
| 225 | // Integration/config issues are hard errors (no severity field), so fold their count into |
| 226 | // both `errors` and `total` to keep `issueCount` consistent with `success` and the reported |
no test coverage detected