@biffo/cli 0.312.5 → 0.313.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/index.js +200 -36
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -719,7 +719,8 @@ function checkCoreOwnership({
|
|
|
719
719
|
branch = "",
|
|
720
720
|
commitMessage = "",
|
|
721
721
|
warnOnly = [],
|
|
722
|
-
templateShippedPaths = null
|
|
722
|
+
templateShippedPaths = null,
|
|
723
|
+
upgradeCommitFiles = null
|
|
723
724
|
}) {
|
|
724
725
|
const empty = {
|
|
725
726
|
blocked: [],
|
|
@@ -729,8 +730,13 @@ function checkCoreOwnership({
|
|
|
729
730
|
knownOrphans: []
|
|
730
731
|
};
|
|
731
732
|
if (!isInstance) return { skipped: "template", ...empty };
|
|
732
|
-
|
|
733
|
-
|
|
733
|
+
let effectiveChangedFiles = changedFiles;
|
|
734
|
+
if (branch.startsWith(UPGRADE_BRANCH_PREFIX)) {
|
|
735
|
+
const exempt = new Set(upgradeCommitFiles ?? []);
|
|
736
|
+
effectiveChangedFiles = changedFiles.filter((f) => !exempt.has(f));
|
|
737
|
+
if (effectiveChangedFiles.length === 0) return { skipped: "upgrade-branch", ...empty };
|
|
738
|
+
}
|
|
739
|
+
const manifestTemplateOwned = effectiveChangedFiles.filter((f) => isTemplateOwned(f, manifest));
|
|
734
740
|
const knownOrphans = [];
|
|
735
741
|
const templateOwned = [];
|
|
736
742
|
for (const path of manifestTemplateOwned) {
|
|
@@ -12722,7 +12728,37 @@ async function fetchTemplateShippedPaths(repoRoot, runner = defaultRunner) {
|
|
|
12722
12728
|
return paths.length > 0 ? new Set(paths) : null;
|
|
12723
12729
|
}
|
|
12724
12730
|
|
|
12731
|
+
// src/lib/upgrade-commit-files.ts
|
|
12732
|
+
var defaultRunner2 = async (args, cwd) => {
|
|
12733
|
+
const result = await execa("git", args, { cwd, reject: false, timeout: 15e3 });
|
|
12734
|
+
return { stdout: String(result.stdout ?? ""), exitCode: result.exitCode ?? null };
|
|
12735
|
+
};
|
|
12736
|
+
var splitLines = (stdout) => stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
12737
|
+
async function resolveUpgradeCommitFiles(repoRoot, base, opts = {}, runner = defaultRunner2) {
|
|
12738
|
+
let rev;
|
|
12739
|
+
try {
|
|
12740
|
+
rev = await runner(["rev-list", "--reverse", `${base}..HEAD`], repoRoot);
|
|
12741
|
+
} catch {
|
|
12742
|
+
return null;
|
|
12743
|
+
}
|
|
12744
|
+
if (rev.exitCode !== 0) return null;
|
|
12745
|
+
const commits = splitLines(rev.stdout);
|
|
12746
|
+
if (commits.length === 0) {
|
|
12747
|
+
return opts.stagedFallbackFiles ?? null;
|
|
12748
|
+
}
|
|
12749
|
+
const first = commits[0];
|
|
12750
|
+
let diff;
|
|
12751
|
+
try {
|
|
12752
|
+
diff = await runner(["diff-tree", "--no-commit-id", "--name-only", "-r", first], repoRoot);
|
|
12753
|
+
} catch {
|
|
12754
|
+
return null;
|
|
12755
|
+
}
|
|
12756
|
+
if (diff.exitCode !== 0) return null;
|
|
12757
|
+
return splitLines(diff.stdout);
|
|
12758
|
+
}
|
|
12759
|
+
|
|
12725
12760
|
// src/scripts/check-core-ownership.ts
|
|
12761
|
+
var LOCAL_UPGRADE_BASE = "origin/dev";
|
|
12726
12762
|
var BOLD = "\x1B[1m";
|
|
12727
12763
|
var DIM = "\x1B[2m";
|
|
12728
12764
|
var RED = "\x1B[31m";
|
|
@@ -12748,6 +12784,7 @@ async function runOwnershipCheck(argv) {
|
|
|
12748
12784
|
let changedFiles;
|
|
12749
12785
|
let deletedFiles = [];
|
|
12750
12786
|
let commitMessage = "";
|
|
12787
|
+
let ciBase = null;
|
|
12751
12788
|
if (staged) {
|
|
12752
12789
|
const { stdout } = await execa("git", ["diff", "--cached", "--name-status"], { cwd: root });
|
|
12753
12790
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
@@ -12762,11 +12799,12 @@ async function runOwnershipCheck(argv) {
|
|
|
12762
12799
|
process.exit(2);
|
|
12763
12800
|
}
|
|
12764
12801
|
await execa("git", ["fetch", "--quiet", "origin", base], { cwd: root, reject: false });
|
|
12765
|
-
|
|
12802
|
+
ciBase = `origin/${base}`;
|
|
12803
|
+
const { stdout } = await execa("git", ["diff", "--name-status", `${ciBase}...HEAD`], {
|
|
12766
12804
|
cwd: root
|
|
12767
12805
|
});
|
|
12768
12806
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
12769
|
-
const { stdout: log2 } = await execa("git", ["log", "--format=%B",
|
|
12807
|
+
const { stdout: log2 } = await execa("git", ["log", "--format=%B", `${ciBase}..HEAD`], {
|
|
12770
12808
|
cwd: root,
|
|
12771
12809
|
reject: false
|
|
12772
12810
|
});
|
|
@@ -12777,6 +12815,11 @@ async function runOwnershipCheck(argv) {
|
|
|
12777
12815
|
reject: false
|
|
12778
12816
|
});
|
|
12779
12817
|
const branch = resolveBranch(process.env, gitBranch);
|
|
12818
|
+
const upgradeCommitFiles = branch.startsWith(UPGRADE_BRANCH_PREFIX) ? await resolveUpgradeCommitFiles(
|
|
12819
|
+
root,
|
|
12820
|
+
ciBase ?? LOCAL_UPGRADE_BASE,
|
|
12821
|
+
staged ? { stagedFallbackFiles: changedFiles } : {}
|
|
12822
|
+
) : null;
|
|
12780
12823
|
const templateShippedPaths = staged ? null : await fetchTemplateShippedPaths(root);
|
|
12781
12824
|
if (!staged && templateShippedPaths === null) {
|
|
12782
12825
|
console.error(
|
|
@@ -12790,6 +12833,7 @@ async function runOwnershipCheck(argv) {
|
|
|
12790
12833
|
branch,
|
|
12791
12834
|
commitMessage,
|
|
12792
12835
|
warnOnly: readDivergenceConfig(root).warnOnly,
|
|
12836
|
+
upgradeCommitFiles,
|
|
12793
12837
|
templateShippedPaths
|
|
12794
12838
|
});
|
|
12795
12839
|
if (result.knownOrphans.length > 0) {
|
|
@@ -16299,7 +16343,7 @@ function rawArgsAfter(subcommand) {
|
|
|
16299
16343
|
|
|
16300
16344
|
// src/commands/doctor.ts
|
|
16301
16345
|
import { existsSync as existsSync56, readFileSync as readFileSync47 } from "fs";
|
|
16302
|
-
import { join as
|
|
16346
|
+
import { join as join69, resolve as resolve20 } from "path";
|
|
16303
16347
|
import chalk21 from "chalk";
|
|
16304
16348
|
import { Command as Command25 } from "commander";
|
|
16305
16349
|
|
|
@@ -16638,33 +16682,110 @@ async function reapAllBareBranches(cwd, branches, worktrees, currentBranch, deps
|
|
|
16638
16682
|
return outcomes;
|
|
16639
16683
|
}
|
|
16640
16684
|
|
|
16641
|
-
// src/
|
|
16685
|
+
// src/lib/scratch-clone-scan.ts
|
|
16686
|
+
import { readdirSync as readdirSync29, statSync as statSync18 } from "fs";
|
|
16687
|
+
import { join as join68 } from "path";
|
|
16642
16688
|
var INTEGRATION_BRANCH = "dev";
|
|
16689
|
+
function isPlainCloneDir(path) {
|
|
16690
|
+
try {
|
|
16691
|
+
return statSync18(join68(path, ".git")).isDirectory();
|
|
16692
|
+
} catch {
|
|
16693
|
+
return false;
|
|
16694
|
+
}
|
|
16695
|
+
}
|
|
16696
|
+
function repoNameFromRemoteUrl(url) {
|
|
16697
|
+
const trimmed = url.trim();
|
|
16698
|
+
if (trimmed === "") return null;
|
|
16699
|
+
const match = /\/([^/]+?)(\.git)?\/?$/.exec(trimmed);
|
|
16700
|
+
return match?.[1] && match[1] !== "" ? match[1] : null;
|
|
16701
|
+
}
|
|
16702
|
+
async function findScratchCloneCandidates(estateRoot, deps) {
|
|
16703
|
+
const names = readdirSync29(estateRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
16704
|
+
const candidates = [];
|
|
16705
|
+
for (const name of names) {
|
|
16706
|
+
const path = join68(estateRoot, name);
|
|
16707
|
+
if (!isPlainCloneDir(path)) continue;
|
|
16708
|
+
let branch;
|
|
16709
|
+
try {
|
|
16710
|
+
branch = await deps.git.currentBranch(path);
|
|
16711
|
+
} catch {
|
|
16712
|
+
candidates.push({ path, branch: "", invalidRepo: true });
|
|
16713
|
+
continue;
|
|
16714
|
+
}
|
|
16715
|
+
const remoteUrl = await deps.git.getRemoteUrl(path).catch(() => "");
|
|
16716
|
+
const repoName = remoteUrl === "" ? null : repoNameFromRemoteUrl(remoteUrl);
|
|
16717
|
+
const looksLikeKnownRepoCheckout = repoName !== null && repoName.toLowerCase() === name.toLowerCase() && branch === INTEGRATION_BRANCH;
|
|
16718
|
+
if (looksLikeKnownRepoCheckout) continue;
|
|
16719
|
+
candidates.push({ path, branch });
|
|
16720
|
+
}
|
|
16721
|
+
return candidates;
|
|
16722
|
+
}
|
|
16723
|
+
async function classifyScratchClones(candidates, deps) {
|
|
16724
|
+
const reports = [];
|
|
16725
|
+
for (const candidate of candidates) {
|
|
16726
|
+
if (candidate.invalidRepo === true) {
|
|
16727
|
+
reports.push({ candidate, verdict: { action: "keep", reason: "not-a-git-repository" } });
|
|
16728
|
+
continue;
|
|
16729
|
+
}
|
|
16730
|
+
const isDetached = candidate.branch === "HEAD" || candidate.branch === "";
|
|
16731
|
+
const isDirty = await deps.git.hasUncommittedChanges(candidate.path);
|
|
16732
|
+
const prVerdict = isDetached || isDirty ? "unknown" : await deps.github.prVerdictForBranch(candidate.path, candidate.branch);
|
|
16733
|
+
let mergeContainsHead = true;
|
|
16734
|
+
if (prVerdict === "merged") {
|
|
16735
|
+
const [headSha, mergedHeadSha] = await Promise.all([
|
|
16736
|
+
deps.git.headSha(candidate.path),
|
|
16737
|
+
deps.github.mergedHeadSha(candidate.path, candidate.branch)
|
|
16738
|
+
]);
|
|
16739
|
+
mergeContainsHead = headSha === null || mergedHeadSha === null ? null : await deps.git.isAncestor(candidate.path, headSha, mergedHeadSha);
|
|
16740
|
+
}
|
|
16741
|
+
const verdict = classifyReapCandidate({
|
|
16742
|
+
isDetached,
|
|
16743
|
+
isDirty,
|
|
16744
|
+
hasFleetClaim: false,
|
|
16745
|
+
prVerdict,
|
|
16746
|
+
mergeContainsHead
|
|
16747
|
+
});
|
|
16748
|
+
reports.push({ candidate, verdict });
|
|
16749
|
+
}
|
|
16750
|
+
return reports;
|
|
16751
|
+
}
|
|
16752
|
+
|
|
16753
|
+
// src/commands/doctor.ts
|
|
16754
|
+
var INTEGRATION_BRANCH2 = "dev";
|
|
16643
16755
|
var doctorCommand = new Command25("doctor").description(
|
|
16644
16756
|
"Report repo-state conditions that make everything read from this checkout unreliable"
|
|
16645
16757
|
).option("--cwd <path>", "Repo root to inspect (defaults to the current directory)").option("--no-fetch", "Skip the fetch; report against refs as they already are locally").option(
|
|
16646
16758
|
"--fix",
|
|
16647
16759
|
"Also remove any worktree, and delete any bare local branch, PROVEN safe: one whose branch's PR merged, verified via GitHub (never local commit reachability \u2014 a squash merge rewrites every SHA), AND whose current tip is confirmed contained in what that PR actually shipped \u2014 not merely on a branch of the same name (#1810). Everything else (no PR, PR open, PR closed unmerged, detached HEAD, uncommitted changes, commits ahead of what merged, or that containment could not be confirmed) is reported, never touched."
|
|
16648
|
-
).
|
|
16649
|
-
|
|
16650
|
-
|
|
16651
|
-
|
|
16652
|
-
|
|
16653
|
-
|
|
16654
|
-
|
|
16655
|
-
|
|
16656
|
-
|
|
16657
|
-
|
|
16658
|
-
|
|
16659
|
-
|
|
16660
|
-
|
|
16661
|
-
|
|
16662
|
-
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
16760
|
+
).option(
|
|
16761
|
+
"--scratch-clones <estateRoot>",
|
|
16762
|
+
"Instead of checking one repo, scan <estateRoot> for top-level plain `git clone` directories (#1949) \u2014 invisible to every check above, which all start from `git worktree list`. Each is classified the same way `--fix` classifies a worktree (GitHub's own PR verdict), but nothing is ever removed by this mode: a wrongly-excluded directory costs a report line, an `rm -rf` on a wrongly-included one would not be recoverable the way `git worktree remove` is."
|
|
16763
|
+
).action(
|
|
16764
|
+
async (options) => {
|
|
16765
|
+
if (options.scratchClones !== void 0) {
|
|
16766
|
+
await runScratchCloneScan(resolve20(options.scratchClones));
|
|
16767
|
+
return;
|
|
16768
|
+
}
|
|
16769
|
+
const cwd = options.cwd ? resolve20(options.cwd) : process.cwd();
|
|
16770
|
+
const git = new GitAdapter();
|
|
16771
|
+
try {
|
|
16772
|
+
const facts = await gatherRepoFacts({ cwd, fetch: options.fetch !== false }, { git });
|
|
16773
|
+
const findings = runDoctorChecks(facts);
|
|
16774
|
+
printFindings(findings);
|
|
16775
|
+
if (options.fix === true) {
|
|
16776
|
+
const github = new GithubCliAdapter();
|
|
16777
|
+
const outcomes = await runDoctorFix(cwd, facts, { git, github });
|
|
16778
|
+
printReapOutcomes(outcomes);
|
|
16779
|
+
const branchOutcomes = await runDoctorFixBranches(cwd, facts, { git, github });
|
|
16780
|
+
printBranchReapOutcomes(branchOutcomes);
|
|
16781
|
+
}
|
|
16782
|
+
if (findings.some((f) => f.severity === "error")) process.exit(1);
|
|
16783
|
+
} catch (err) {
|
|
16784
|
+
log.error(err.message);
|
|
16785
|
+
process.exit(1);
|
|
16786
|
+
}
|
|
16666
16787
|
}
|
|
16667
|
-
|
|
16788
|
+
);
|
|
16668
16789
|
async function gatherRepoFacts(options, deps) {
|
|
16669
16790
|
const { git } = deps;
|
|
16670
16791
|
if (!await git.isGitRepo(options.cwd)) {
|
|
@@ -16680,13 +16801,13 @@ async function gatherRepoFacts(options, deps) {
|
|
|
16680
16801
|
const worktrees = await Promise.all(
|
|
16681
16802
|
worktreePaths.map(async (w) => ({
|
|
16682
16803
|
...w,
|
|
16683
|
-
behind: await git.countBehind(options.cwd, w.branch, `origin/${
|
|
16804
|
+
behind: await git.countBehind(options.cwd, w.branch, `origin/${INTEGRATION_BRANCH2}`)
|
|
16684
16805
|
}))
|
|
16685
16806
|
);
|
|
16686
16807
|
return {
|
|
16687
16808
|
currentBranch,
|
|
16688
16809
|
isPrimary,
|
|
16689
|
-
integrationBranch:
|
|
16810
|
+
integrationBranch: INTEGRATION_BRANCH2,
|
|
16690
16811
|
ahead,
|
|
16691
16812
|
behind,
|
|
16692
16813
|
hasUpstream,
|
|
@@ -16697,7 +16818,7 @@ async function gatherRepoFacts(options, deps) {
|
|
|
16697
16818
|
// a fault in it.
|
|
16698
16819
|
localCoreVersion: readLocalCoreVersion(options.cwd),
|
|
16699
16820
|
remoteCoreVersion: parseCoreRecord(
|
|
16700
|
-
await git.showFileAtRef(options.cwd, `origin/${
|
|
16821
|
+
await git.showFileAtRef(options.cwd, `origin/${INTEGRATION_BRANCH2}`, INSTANCE_CORE_FILE)
|
|
16701
16822
|
),
|
|
16702
16823
|
fossilCoreVersion: readFossil(options.cwd),
|
|
16703
16824
|
branches,
|
|
@@ -16719,8 +16840,50 @@ async function runDoctorFixBranches(cwd, facts, deps = { git: new GitAdapter(),
|
|
|
16719
16840
|
remoteBranchNames
|
|
16720
16841
|
);
|
|
16721
16842
|
}
|
|
16843
|
+
async function runScratchCloneScan(estateRoot) {
|
|
16844
|
+
const git = new GitAdapter();
|
|
16845
|
+
const github = new GithubCliAdapter();
|
|
16846
|
+
try {
|
|
16847
|
+
const candidates = await findScratchCloneCandidates(estateRoot, { git });
|
|
16848
|
+
const reports = await classifyScratchClones(candidates, { git, github });
|
|
16849
|
+
printScratchCloneReports(estateRoot, reports);
|
|
16850
|
+
} catch (err) {
|
|
16851
|
+
log.error(err.message);
|
|
16852
|
+
process.exit(1);
|
|
16853
|
+
}
|
|
16854
|
+
}
|
|
16855
|
+
function printScratchCloneReports(estateRoot, reports) {
|
|
16856
|
+
if (reports.length === 0) {
|
|
16857
|
+
console.log(
|
|
16858
|
+
chalk21.dim(` scratch-clones: no plain git-clone directory found under ${estateRoot}.
|
|
16859
|
+
`)
|
|
16860
|
+
);
|
|
16861
|
+
return;
|
|
16862
|
+
}
|
|
16863
|
+
const reapable = reports.filter((r) => r.verdict.action === "reap");
|
|
16864
|
+
const kept = reports.filter((r) => r.verdict.action === "keep");
|
|
16865
|
+
console.log("");
|
|
16866
|
+
for (const r of reapable) {
|
|
16867
|
+
console.log(
|
|
16868
|
+
chalk21.yellow(
|
|
16869
|
+
` reapable ${r.candidate.path} (${r.candidate.branch}) \u2014 branch's PR merged and this clone's tip is contained in what shipped; plain git clone, not a worktree, so \`git worktree remove\` does not apply \u2014 remove by hand once confirmed: rm -rf '${r.candidate.path}'`
|
|
16870
|
+
)
|
|
16871
|
+
);
|
|
16872
|
+
}
|
|
16873
|
+
for (const r of kept) {
|
|
16874
|
+
const reason = r.verdict.reason === void 0 ? "unknown" : KEEP_REASON_TEXT[r.verdict.reason];
|
|
16875
|
+
console.log(chalk21.dim(` kept ${r.candidate.path} (${r.candidate.branch}) \u2014 ${reason}`));
|
|
16876
|
+
}
|
|
16877
|
+
console.log(
|
|
16878
|
+
chalk21.dim(
|
|
16879
|
+
`
|
|
16880
|
+
scratch-clones: ${String(reapable.length)} reapable, ${String(kept.length)} kept, of ${String(reports.length)} plain git-clone director${reports.length === 1 ? "y" : "ies"} found under ${estateRoot}.
|
|
16881
|
+
`
|
|
16882
|
+
)
|
|
16883
|
+
);
|
|
16884
|
+
}
|
|
16722
16885
|
function readLocalCoreVersion(cwd) {
|
|
16723
|
-
const path =
|
|
16886
|
+
const path = join69(cwd, INSTANCE_CORE_FILE);
|
|
16724
16887
|
if (!existsSync56(path)) return null;
|
|
16725
16888
|
try {
|
|
16726
16889
|
return extractVersionField(readFileSync47(path, "utf8"));
|
|
@@ -16743,7 +16906,7 @@ function extractVersionField(contents) {
|
|
|
16743
16906
|
return match?.[1] ?? null;
|
|
16744
16907
|
}
|
|
16745
16908
|
function readFossil(cwd) {
|
|
16746
|
-
const path =
|
|
16909
|
+
const path = join69(cwd, CORE_VERSION_FILE);
|
|
16747
16910
|
if (!existsSync56(path)) return null;
|
|
16748
16911
|
try {
|
|
16749
16912
|
const value = readFileSync47(path, "utf8").trim();
|
|
@@ -16783,7 +16946,8 @@ var KEEP_REASON_TEXT = {
|
|
|
16783
16946
|
"no-pr": "no PR was ever opened from this branch",
|
|
16784
16947
|
"unknown-pr-verdict": "could not read this branch's PR state from GitHub",
|
|
16785
16948
|
"commits-not-in-merge": "worktree HEAD includes commits the merged PR never shipped",
|
|
16786
|
-
"unknown-merge-head": "could not confirm worktree HEAD is contained in what merged"
|
|
16949
|
+
"unknown-merge-head": "could not confirm worktree HEAD is contained in what merged",
|
|
16950
|
+
"not-a-git-repository": "the .git directory exists but is not a resolvable, initialised git repository"
|
|
16787
16951
|
};
|
|
16788
16952
|
function printReapOutcomes(outcomes) {
|
|
16789
16953
|
if (outcomes.length === 0) {
|
|
@@ -17277,18 +17441,18 @@ function resolveGithubToken4() {
|
|
|
17277
17441
|
|
|
17278
17442
|
// src/lib/packaged-script-command.ts
|
|
17279
17443
|
import { spawnSync } from "child_process";
|
|
17280
|
-
import { chmodSync as chmodSync2, statSync as
|
|
17444
|
+
import { chmodSync as chmodSync2, statSync as statSync19 } from "fs";
|
|
17281
17445
|
import { dirname as dirname12 } from "path";
|
|
17282
17446
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
17283
17447
|
import { Command as Command27 } from "commander";
|
|
17284
17448
|
|
|
17285
17449
|
// src/lib/packaged-scripts.ts
|
|
17286
17450
|
import { existsSync as existsSync57 } from "fs";
|
|
17287
|
-
import { dirname as dirname11, join as
|
|
17451
|
+
import { dirname as dirname11, join as join70 } from "path";
|
|
17288
17452
|
function findPackagedScript(startDir, relativePath) {
|
|
17289
17453
|
let dir = startDir;
|
|
17290
17454
|
for (; ; ) {
|
|
17291
|
-
const candidate =
|
|
17455
|
+
const candidate = join70(dir, relativePath);
|
|
17292
17456
|
if (existsSync57(candidate)) return candidate;
|
|
17293
17457
|
const parent = dirname11(dir);
|
|
17294
17458
|
if (parent === dir) return null;
|
|
@@ -17303,7 +17467,7 @@ It ships with this package via cli/scripts/packaged-root-assets.mjs; if you are
|
|
|
17303
17467
|
// src/lib/packaged-script-command.ts
|
|
17304
17468
|
function ensureExecutable(script) {
|
|
17305
17469
|
try {
|
|
17306
|
-
const { mode } =
|
|
17470
|
+
const { mode } = statSync19(script);
|
|
17307
17471
|
if ((mode & 73) === 0) chmodSync2(script, mode | 493);
|
|
17308
17472
|
} catch {
|
|
17309
17473
|
}
|