@crossplatformai/dependency-graph 0.10.0 → 0.11.0-next.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/dist/cli/pr-preview.d.ts +2 -0
- package/dist/cli/pr-preview.d.ts.map +1 -1
- package/dist/index-cli.js +167 -45
- package/dist/index-cli.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +114 -17
- package/dist/index.js.map +1 -1
- package/dist/workflow/impact.d.ts +5 -0
- package/dist/workflow/impact.d.ts.map +1 -0
- package/dist/workflow/types.d.ts +8 -1
- package/dist/workflow/types.d.ts.map +1 -1
- package/dist/workflow/validator.d.ts.map +1 -1
- package/package.json +5 -7
package/dist/cli/pr-preview.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { type WorkflowValidationResult } from '../index';
|
|
3
|
+
export declare function formatWorkflowPathDrift(validationResults: WorkflowValidationResult[]): string[];
|
|
2
4
|
export declare function runPrPreview(): Promise<void>;
|
|
3
5
|
//# sourceMappingURL=pr-preview.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pr-preview.d.ts","sourceRoot":"","sources":["../../src/cli/pr-preview.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"pr-preview.d.ts","sourceRoot":"","sources":["../../src/cli/pr-preview.ts"],"names":[],"mappings":";AAQA,OAAO,EAOL,KAAK,wBAAwB,EAC9B,MAAM,UAAU,CAAC;AA0DlB,wBAAgB,uBAAuB,CACrC,iBAAiB,EAAE,wBAAwB,EAAE,GAC5C,MAAM,EAAE,CAyBV;AAsGD,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CA4NlD"}
|
package/dist/index-cli.js
CHANGED
|
@@ -208,12 +208,6 @@ var defaultWorkflowValidationPolicy = {
|
|
|
208
208
|
includeDevDependenciesTransitively: false
|
|
209
209
|
};
|
|
210
210
|
|
|
211
|
-
// src/workflow/validator.ts
|
|
212
|
-
import { existsSync as existsSync4 } from "fs";
|
|
213
|
-
import { readFile } from "fs/promises";
|
|
214
|
-
import { glob } from "glob";
|
|
215
|
-
import { parse as parseYaml2 } from "yaml";
|
|
216
|
-
|
|
217
211
|
// src/workflow/discovery.ts
|
|
218
212
|
import { existsSync as existsSync2, readdirSync } from "fs";
|
|
219
213
|
import { join as join3 } from "path";
|
|
@@ -333,11 +327,69 @@ function getExpectedWorkflowPaths({
|
|
|
333
327
|
return uniqueSorted(expectedPaths);
|
|
334
328
|
}
|
|
335
329
|
|
|
330
|
+
// src/workflow/impact.ts
|
|
331
|
+
function uniqueSorted2(values) {
|
|
332
|
+
return Array.from(new Set(values)).sort();
|
|
333
|
+
}
|
|
334
|
+
function mergeWorkflowPolicy(policyOverrides) {
|
|
335
|
+
return {
|
|
336
|
+
...defaultWorkflowValidationPolicy,
|
|
337
|
+
...policyOverrides,
|
|
338
|
+
allowedRootPaths: uniqueSorted2([
|
|
339
|
+
...defaultWorkflowValidationPolicy.allowedRootPaths,
|
|
340
|
+
...policyOverrides?.allowedRootPaths ?? []
|
|
341
|
+
])
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function matchesWorkflowPathFilter(changedPath, workflowPathFilter) {
|
|
345
|
+
if (workflowPathFilter === changedPath) {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
if (!workflowPathFilter.endsWith("/**")) {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
const workflowPrefix = workflowPathFilter.slice(0, -3);
|
|
352
|
+
return changedPath.startsWith(`${workflowPrefix}/`);
|
|
353
|
+
}
|
|
354
|
+
function getWorkflowImpacts(rootDir, packages, changedPaths, policyOverrides) {
|
|
355
|
+
const policy = mergeWorkflowPolicy(policyOverrides);
|
|
356
|
+
const workflowTargets = discoverWorkflowTargets(rootDir, packages, policy);
|
|
357
|
+
const { packageMap } = buildPackageMap(packages, rootDir);
|
|
358
|
+
return workflowTargets.map((workflowTarget) => {
|
|
359
|
+
const calculatedPaths = uniqueSorted2([
|
|
360
|
+
...getExpectedWorkflowPaths({
|
|
361
|
+
workflowTarget,
|
|
362
|
+
packages,
|
|
363
|
+
packageMap,
|
|
364
|
+
policy
|
|
365
|
+
}),
|
|
366
|
+
...policy.allowedRootPaths,
|
|
367
|
+
workflowTarget.workflowPath
|
|
368
|
+
]);
|
|
369
|
+
const matchedPaths = calculatedPaths.filter(
|
|
370
|
+
(calculatedPath) => changedPaths.some(
|
|
371
|
+
(changedPath) => matchesWorkflowPathFilter(changedPath, calculatedPath)
|
|
372
|
+
)
|
|
373
|
+
);
|
|
374
|
+
return {
|
|
375
|
+
...workflowTarget,
|
|
376
|
+
matchedPaths: uniqueSorted2(matchedPaths)
|
|
377
|
+
};
|
|
378
|
+
}).filter((workflowImpact) => workflowImpact.matchedPaths.length > 0);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/workflow/validator.ts
|
|
382
|
+
import { existsSync as existsSync4 } from "fs";
|
|
383
|
+
import { readFile } from "fs/promises";
|
|
384
|
+
import { join as join5 } from "path";
|
|
385
|
+
import { glob } from "glob";
|
|
386
|
+
import { parse as parseYaml2 } from "yaml";
|
|
387
|
+
|
|
336
388
|
// src/workflow/parser.ts
|
|
337
389
|
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
338
390
|
import { join as join4 } from "path";
|
|
339
391
|
import { parse as parseYaml } from "yaml";
|
|
340
|
-
function
|
|
392
|
+
function uniqueSorted3(values) {
|
|
341
393
|
return Array.from(new Set(values)).sort();
|
|
342
394
|
}
|
|
343
395
|
function parseWorkflowFile(workflowFile, rootDir) {
|
|
@@ -350,9 +402,9 @@ function parseWorkflowFile(workflowFile, rootDir) {
|
|
|
350
402
|
const pushPaths = workflow.on?.push?.paths ?? [];
|
|
351
403
|
const pullRequestPaths = workflow.on?.pull_request?.paths ?? [];
|
|
352
404
|
const parsedWorkflow = {
|
|
353
|
-
pushPaths:
|
|
354
|
-
pullRequestPaths:
|
|
355
|
-
paths:
|
|
405
|
+
pushPaths: uniqueSorted3(pushPaths),
|
|
406
|
+
pullRequestPaths: uniqueSorted3(pullRequestPaths),
|
|
407
|
+
paths: uniqueSorted3([...pushPaths, ...pullRequestPaths])
|
|
356
408
|
};
|
|
357
409
|
if (workflow.name) {
|
|
358
410
|
parsedWorkflow.name = workflow.name;
|
|
@@ -361,7 +413,7 @@ function parseWorkflowFile(workflowFile, rootDir) {
|
|
|
361
413
|
}
|
|
362
414
|
|
|
363
415
|
// src/workflow/validator.ts
|
|
364
|
-
function
|
|
416
|
+
function uniqueSorted4(values) {
|
|
365
417
|
return Array.from(new Set(values)).sort();
|
|
366
418
|
}
|
|
367
419
|
function buildBroadWildcards(workspaceRoots) {
|
|
@@ -372,6 +424,23 @@ function buildBroadWildcards(workspaceRoots) {
|
|
|
372
424
|
}
|
|
373
425
|
return wildcards;
|
|
374
426
|
}
|
|
427
|
+
function hasGlobSyntax(path) {
|
|
428
|
+
return /[*?[\]{}]/.test(path);
|
|
429
|
+
}
|
|
430
|
+
function getStalePathCheckBase(path, allowedRootPaths, workflowPath) {
|
|
431
|
+
if (path === workflowPath || allowedRootPaths.includes(path) || path.startsWith("!")) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
const trailingGlobSuffix = path.endsWith("/**") ? "/**" : path.endsWith("/*") ? "/*" : null;
|
|
435
|
+
const checkBase = trailingGlobSuffix ? path.slice(0, -trailingGlobSuffix.length) : path;
|
|
436
|
+
if (!checkBase || hasGlobSyntax(checkBase)) {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
if (!trailingGlobSuffix && hasGlobSyntax(path)) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
return checkBase;
|
|
443
|
+
}
|
|
375
444
|
function splitActualPaths(paths, workspaceRoots, allowedRootPaths, workflowPath) {
|
|
376
445
|
const workspacePaths = [];
|
|
377
446
|
const ignoredPaths = [];
|
|
@@ -388,8 +457,8 @@ function splitActualPaths(paths, workspaceRoots, allowedRootPaths, workflowPath)
|
|
|
388
457
|
ignoredPaths.push(path);
|
|
389
458
|
}
|
|
390
459
|
return {
|
|
391
|
-
workspacePaths:
|
|
392
|
-
ignoredPaths:
|
|
460
|
+
workspacePaths: uniqueSorted4(workspacePaths),
|
|
461
|
+
ignoredPaths: uniqueSorted4(ignoredPaths)
|
|
393
462
|
};
|
|
394
463
|
}
|
|
395
464
|
function isCoveredByExpectedPath(actualPath, expectedPaths) {
|
|
@@ -404,7 +473,7 @@ function isCoveredByExpectedPath(actualPath, expectedPaths) {
|
|
|
404
473
|
return actualPath.startsWith(`${expectedPrefix}/`);
|
|
405
474
|
});
|
|
406
475
|
}
|
|
407
|
-
function validateWorkflowResult(workflow, targetPackage, expectedPaths, actualPaths, broadWildcards) {
|
|
476
|
+
function validateWorkflowResult(workflow, targetPackage, expectedPaths, actualPaths, broadWildcards, additionalIssues = []) {
|
|
408
477
|
const issues = [];
|
|
409
478
|
const missing = expectedPaths.filter((path) => !actualPaths.includes(path));
|
|
410
479
|
const unnecessary = actualPaths.filter(
|
|
@@ -433,6 +502,7 @@ function validateWorkflowResult(workflow, targetPackage, expectedPaths, actualPa
|
|
|
433
502
|
});
|
|
434
503
|
}
|
|
435
504
|
}
|
|
505
|
+
issues.push(...additionalIssues);
|
|
436
506
|
return {
|
|
437
507
|
workflow,
|
|
438
508
|
targetPackage,
|
|
@@ -444,6 +514,25 @@ function validateWorkflowResult(workflow, targetPackage, expectedPaths, actualPa
|
|
|
444
514
|
issues
|
|
445
515
|
};
|
|
446
516
|
}
|
|
517
|
+
function getStalePathIssues(ignoredPaths, rootDir, allowedRootPaths, workflowPath) {
|
|
518
|
+
return ignoredPaths.flatMap((path) => {
|
|
519
|
+
const checkBase = getStalePathCheckBase(
|
|
520
|
+
path,
|
|
521
|
+
allowedRootPaths,
|
|
522
|
+
workflowPath
|
|
523
|
+
);
|
|
524
|
+
if (!checkBase || existsSync4(join5(rootDir, checkBase))) {
|
|
525
|
+
return [];
|
|
526
|
+
}
|
|
527
|
+
return [
|
|
528
|
+
{
|
|
529
|
+
kind: "stale-path",
|
|
530
|
+
path,
|
|
531
|
+
message: `Stale path '${path}' does not exist`
|
|
532
|
+
}
|
|
533
|
+
];
|
|
534
|
+
});
|
|
535
|
+
}
|
|
447
536
|
async function validateWorkflows(rootDir, policyOverrides) {
|
|
448
537
|
const discoveredPackages = await discoverWorkspaces(rootDir, {
|
|
449
538
|
fs: {
|
|
@@ -460,7 +549,7 @@ async function validateWorkflows(rootDir, policyOverrides) {
|
|
|
460
549
|
const policy = {
|
|
461
550
|
...defaultWorkflowValidationPolicy,
|
|
462
551
|
...policyOverrides,
|
|
463
|
-
allowedRootPaths:
|
|
552
|
+
allowedRootPaths: uniqueSorted4([
|
|
464
553
|
...defaultWorkflowValidationPolicy.allowedRootPaths,
|
|
465
554
|
...policyOverrides?.allowedRootPaths ?? []
|
|
466
555
|
])
|
|
@@ -510,7 +599,7 @@ async function validateWorkflows(rootDir, policyOverrides) {
|
|
|
510
599
|
issues: []
|
|
511
600
|
};
|
|
512
601
|
}
|
|
513
|
-
const { workspacePaths: actualPaths } = splitActualPaths(
|
|
602
|
+
const { workspacePaths: actualPaths, ignoredPaths } = splitActualPaths(
|
|
514
603
|
parsedWorkflow.paths,
|
|
515
604
|
workspaceRoots,
|
|
516
605
|
policy.allowedRootPaths,
|
|
@@ -527,7 +616,13 @@ async function validateWorkflows(rootDir, policyOverrides) {
|
|
|
527
616
|
workflowTarget.targetPackage,
|
|
528
617
|
expectedPaths,
|
|
529
618
|
actualPaths,
|
|
530
|
-
broadWildcards
|
|
619
|
+
broadWildcards,
|
|
620
|
+
getStalePathIssues(
|
|
621
|
+
ignoredPaths,
|
|
622
|
+
rootDir,
|
|
623
|
+
policy.allowedRootPaths,
|
|
624
|
+
workflowTarget.workflowPath
|
|
625
|
+
)
|
|
531
626
|
);
|
|
532
627
|
} catch (error) {
|
|
533
628
|
return {
|
|
@@ -550,14 +645,6 @@ async function validateWorkflows(rootDir, policyOverrides) {
|
|
|
550
645
|
}
|
|
551
646
|
|
|
552
647
|
// src/cli/pr-preview.ts
|
|
553
|
-
var WORKFLOW_MAPPING = {
|
|
554
|
-
"deploy-web.yml": "web",
|
|
555
|
-
"deploy-api-origin.yml": "api-origin",
|
|
556
|
-
"deploy-api-edge.yml": "api-edge",
|
|
557
|
-
"release-mobile.yml": "mobile",
|
|
558
|
-
"release-desktop.yml": "desktop",
|
|
559
|
-
"release-cli.yml": "cli"
|
|
560
|
-
};
|
|
561
648
|
var PLATFORM_INDICATORS = [
|
|
562
649
|
{
|
|
563
650
|
platform: "cloudflare-workers",
|
|
@@ -596,6 +683,28 @@ var PLATFORM_INDICATORS = [
|
|
|
596
683
|
}
|
|
597
684
|
];
|
|
598
685
|
var DEFAULT_APP_DIRECTORIES = ["apps"];
|
|
686
|
+
function formatWorkflowPathDrift(validationResults) {
|
|
687
|
+
const invalidResults = validationResults.filter((result) => !result.valid);
|
|
688
|
+
if (invalidResults.length === 0) {
|
|
689
|
+
return [];
|
|
690
|
+
}
|
|
691
|
+
const lines = [
|
|
692
|
+
"\u26A0\uFE0F Workflow path drift (advisory - does not affect impact above):"
|
|
693
|
+
];
|
|
694
|
+
for (const result of invalidResults) {
|
|
695
|
+
lines.push(` ${result.workflow} (${result.targetPackage}):`);
|
|
696
|
+
for (const issue of result.issues) {
|
|
697
|
+
if (issue.kind === "missing") {
|
|
698
|
+
lines.push(` + ${issue.message}`);
|
|
699
|
+
} else if (issue.kind === "unnecessary") {
|
|
700
|
+
lines.push(` - ${issue.message}`);
|
|
701
|
+
} else {
|
|
702
|
+
lines.push(` ! ${issue.message}`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return lines;
|
|
707
|
+
}
|
|
599
708
|
function isDeployableApp(pkg) {
|
|
600
709
|
const isInAppDirectory = DEFAULT_APP_DIRECTORIES.some(
|
|
601
710
|
(dir) => pkg.path.includes(`/${dir}/`) || pkg.path.endsWith(`/${dir}`)
|
|
@@ -713,23 +822,32 @@ async function runPrPreview() {
|
|
|
713
822
|
changedPackages.add(pkg.name);
|
|
714
823
|
}
|
|
715
824
|
}
|
|
716
|
-
const
|
|
717
|
-
|
|
825
|
+
const workflowImpacts = getWorkflowImpacts(
|
|
826
|
+
rootDir,
|
|
827
|
+
packages,
|
|
828
|
+
changedFiles.map((file) => file.path)
|
|
829
|
+
);
|
|
830
|
+
const workflowDriftLines = formatWorkflowPathDrift(
|
|
831
|
+
await validateWorkflows(rootDir)
|
|
832
|
+
);
|
|
833
|
+
const workflowAffectedApps = new Set(
|
|
834
|
+
workflowImpacts.map(
|
|
835
|
+
(workflowImpact) => workflowImpact.targetPackage ?? workflowImpact.targetSlug
|
|
836
|
+
)
|
|
718
837
|
);
|
|
719
|
-
const workflowAffectedApps = /* @__PURE__ */ new Set();
|
|
720
|
-
for (const workflow of changedWorkflows) {
|
|
721
|
-
const workflowName = workflow.path.split("/").pop();
|
|
722
|
-
if (workflowName && WORKFLOW_MAPPING[workflowName]) {
|
|
723
|
-
workflowAffectedApps.add(WORKFLOW_MAPPING[workflowName]);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
838
|
console.log(
|
|
727
839
|
`
|
|
728
840
|
\u{1F4E6} Changed packages: ${Array.from(changedPackages).join(", ") || "none"}
|
|
729
841
|
`
|
|
730
842
|
);
|
|
731
|
-
if (changedPackages.size === 0) {
|
|
732
|
-
console.log(
|
|
843
|
+
if (changedPackages.size === 0 && workflowAffectedApps.size === 0) {
|
|
844
|
+
console.log(
|
|
845
|
+
"\n\u2728 No packages or workflow filters changed - no deployments needed\n"
|
|
846
|
+
);
|
|
847
|
+
if (workflowDriftLines.length > 0) {
|
|
848
|
+
console.log(workflowDriftLines.join("\n"));
|
|
849
|
+
console.log("");
|
|
850
|
+
}
|
|
733
851
|
return;
|
|
734
852
|
}
|
|
735
853
|
const affected = findAffectedPackages(changedPackages, graph, {
|
|
@@ -826,6 +944,10 @@ async function runPrPreview() {
|
|
|
826
944
|
console.log(`\u{1F4DD} = File changed`);
|
|
827
945
|
console.log(`\u274C = File deleted
|
|
828
946
|
`);
|
|
947
|
+
if (workflowDriftLines.length > 0) {
|
|
948
|
+
console.log(workflowDriftLines.join("\n"));
|
|
949
|
+
console.log("");
|
|
950
|
+
}
|
|
829
951
|
} catch (error) {
|
|
830
952
|
console.error(
|
|
831
953
|
"Error:",
|
|
@@ -840,10 +962,10 @@ if (import.meta.url === new URL(process.argv[1] ?? "", "file:").href) {
|
|
|
840
962
|
|
|
841
963
|
// src/cli/validate-workflows.ts
|
|
842
964
|
import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
|
|
843
|
-
import { join as
|
|
965
|
+
import { join as join6, resolve as resolve3 } from "path";
|
|
844
966
|
import { parse as parseYaml4 } from "yaml";
|
|
845
967
|
function discoverPnpmWorkflows(rootDir) {
|
|
846
|
-
const workflowsDir =
|
|
968
|
+
const workflowsDir = join6(rootDir, ".github/workflows");
|
|
847
969
|
if (!existsSync5(workflowsDir)) {
|
|
848
970
|
return [];
|
|
849
971
|
}
|
|
@@ -852,7 +974,7 @@ function discoverPnpmWorkflows(rootDir) {
|
|
|
852
974
|
(file) => file.endsWith(".yml")
|
|
853
975
|
);
|
|
854
976
|
for (const file of files) {
|
|
855
|
-
const content = readFileSync3(
|
|
977
|
+
const content = readFileSync3(join6(workflowsDir, file), "utf-8");
|
|
856
978
|
if (content.includes("pnpm/action-setup")) {
|
|
857
979
|
workflows.push(file);
|
|
858
980
|
}
|
|
@@ -865,12 +987,12 @@ function discoverPnpmDockerfiles(rootDir) {
|
|
|
865
987
|
if (!entry.startsWith("Dockerfile")) {
|
|
866
988
|
continue;
|
|
867
989
|
}
|
|
868
|
-
const content = readFileSync3(
|
|
990
|
+
const content = readFileSync3(join6(rootDir, entry), "utf-8");
|
|
869
991
|
if (content.includes("pnpm@")) {
|
|
870
992
|
dockerfiles.push(entry);
|
|
871
993
|
}
|
|
872
994
|
}
|
|
873
|
-
const appsDir =
|
|
995
|
+
const appsDir = join6(rootDir, "apps");
|
|
874
996
|
if (!existsSync5(appsDir)) {
|
|
875
997
|
return dockerfiles;
|
|
876
998
|
}
|
|
@@ -878,7 +1000,7 @@ function discoverPnpmDockerfiles(rootDir) {
|
|
|
878
1000
|
if (!entry.isDirectory()) {
|
|
879
1001
|
continue;
|
|
880
1002
|
}
|
|
881
|
-
const dockerfilePath =
|
|
1003
|
+
const dockerfilePath = join6(appsDir, entry.name, "Dockerfile");
|
|
882
1004
|
if (!existsSync5(dockerfilePath)) {
|
|
883
1005
|
continue;
|
|
884
1006
|
}
|
|
@@ -891,7 +1013,7 @@ function discoverPnpmDockerfiles(rootDir) {
|
|
|
891
1013
|
}
|
|
892
1014
|
function getExpectedPnpmVersion(rootDir) {
|
|
893
1015
|
const packageJson = JSON.parse(
|
|
894
|
-
readFileSync3(
|
|
1016
|
+
readFileSync3(join6(rootDir, "package.json"), "utf-8")
|
|
895
1017
|
);
|
|
896
1018
|
const packageManager = packageJson.packageManager;
|
|
897
1019
|
if (!packageManager?.startsWith("pnpm@")) {
|
|
@@ -900,7 +1022,7 @@ function getExpectedPnpmVersion(rootDir) {
|
|
|
900
1022
|
return packageManager.replace("pnpm@", "");
|
|
901
1023
|
}
|
|
902
1024
|
function checkWorkflowPnpmVersion(workflowFile, rootDir) {
|
|
903
|
-
const workflowPath =
|
|
1025
|
+
const workflowPath = join6(rootDir, ".github/workflows", workflowFile);
|
|
904
1026
|
if (!existsSync5(workflowPath)) {
|
|
905
1027
|
return { valid: true };
|
|
906
1028
|
}
|
|
@@ -923,7 +1045,7 @@ function checkWorkflowPnpmVersion(workflowFile, rootDir) {
|
|
|
923
1045
|
return { valid: true };
|
|
924
1046
|
}
|
|
925
1047
|
function checkDockerfilePnpmVersion(dockerfile, expectedVersion, rootDir) {
|
|
926
|
-
const dockerfilePath =
|
|
1048
|
+
const dockerfilePath = join6(rootDir, dockerfile);
|
|
927
1049
|
if (!existsSync5(dockerfilePath)) {
|
|
928
1050
|
return { valid: true };
|
|
929
1051
|
}
|