@lumpcode/core 0.1.0 → 0.1.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/README.md +3 -3
- package/dist/helpers/executeStepsForContextList/main.d.ts.map +1 -1
- package/dist/helpers/getContextStatus/main.d.ts.map +1 -1
- package/dist/index.cjs +206 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +204 -108
- package/dist/index.js.map +1 -1
- package/dist/types/PostCommandExecFn.d.ts +2 -0
- package/dist/types/PostCommandExecFn.d.ts.map +1 -1
- package/dist/utils/commitMessageIncludesMarker/index.d.ts +2 -0
- package/dist/utils/commitMessageIncludesMarker/index.d.ts.map +1 -0
- package/dist/utils/commitMessageIncludesMarker/main.d.ts +3 -0
- package/dist/utils/commitMessageIncludesMarker/main.d.ts.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/parseGitLogHashBodyRecords/index.d.ts +2 -0
- package/dist/utils/parseGitLogHashBodyRecords/index.d.ts.map +1 -0
- package/dist/utils/parseGitLogHashBodyRecords/main.d.ts +8 -0
- package/dist/utils/parseGitLogHashBodyRecords/main.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3403,6 +3403,40 @@ function parseGitLogHashSubjectLines(stdout) {
|
|
|
3403
3403
|
});
|
|
3404
3404
|
}
|
|
3405
3405
|
|
|
3406
|
+
const GIT_LOG_HASH_BODY_FORMAT = '%x1e%H%x00%B';
|
|
3407
|
+
const RECORD_SEPARATOR = '\x1e';
|
|
3408
|
+
const HASH_MESSAGE_SEPARATOR = '\x00';
|
|
3409
|
+
/** Parses `git log --format='%x1e%H%x00%B'` stdout into commit hash and full-message pairs. */
|
|
3410
|
+
function parseGitLogHashBodyRecords(stdout) {
|
|
3411
|
+
const records = [];
|
|
3412
|
+
for (const record of stdout.split(RECORD_SEPARATOR)) {
|
|
3413
|
+
if (!record)
|
|
3414
|
+
continue;
|
|
3415
|
+
const nul = record.indexOf(HASH_MESSAGE_SEPARATOR);
|
|
3416
|
+
if (nul === -1)
|
|
3417
|
+
continue;
|
|
3418
|
+
const hash = record.slice(0, nul).trim();
|
|
3419
|
+
if (!hash)
|
|
3420
|
+
continue;
|
|
3421
|
+
records.push({
|
|
3422
|
+
hash,
|
|
3423
|
+
message: record.slice(nul + 1),
|
|
3424
|
+
});
|
|
3425
|
+
}
|
|
3426
|
+
return records;
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
const MARKER_CONTINUE = '[A-Za-z0-9_-]';
|
|
3430
|
+
function escapeRegExp(value) {
|
|
3431
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3432
|
+
}
|
|
3433
|
+
/** True when `marker` occurs in `message` and is not a prefix of a longer `[A-Za-z0-9_-]*` token. */
|
|
3434
|
+
function commitMessageIncludesMarker(message, marker) {
|
|
3435
|
+
if (marker.length === 0)
|
|
3436
|
+
return false;
|
|
3437
|
+
return new RegExp(`${escapeRegExp(marker)}(?!${MARKER_CONTINUE})`).test(message);
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3406
3440
|
/** Node-style `code` from a caught unknown error (e.g. `ENOENT`), or undefined. */
|
|
3407
3441
|
function nodeErrnoCode(error) {
|
|
3408
3442
|
if (error && typeof error === 'object' && 'code' in error) {
|
|
@@ -3745,6 +3779,54 @@ async function collectStepsForContext(params) {
|
|
|
3745
3779
|
return collectedSteps;
|
|
3746
3780
|
}
|
|
3747
3781
|
|
|
3782
|
+
const ABORT_STEP_WALK_MESSAGE = 'Process aborted';
|
|
3783
|
+
function stepWalkAbortedFailure() {
|
|
3784
|
+
return {
|
|
3785
|
+
success: false,
|
|
3786
|
+
data: {
|
|
3787
|
+
message: ABORT_STEP_WALK_MESSAGE,
|
|
3788
|
+
reason: 'stepWalkFailed',
|
|
3789
|
+
},
|
|
3790
|
+
};
|
|
3791
|
+
}
|
|
3792
|
+
function createStepWalkAbortError() {
|
|
3793
|
+
const error = new Error(ABORT_STEP_WALK_MESSAGE);
|
|
3794
|
+
error.name = 'AbortError';
|
|
3795
|
+
return error;
|
|
3796
|
+
}
|
|
3797
|
+
function isStepWalkAbortError(error) {
|
|
3798
|
+
return (typeof error === 'object'
|
|
3799
|
+
&& error !== null
|
|
3800
|
+
&& 'name' in error
|
|
3801
|
+
&& error.name === 'AbortError');
|
|
3802
|
+
}
|
|
3803
|
+
/**
|
|
3804
|
+
* Await user-hook work, but stop waiting once `signal` aborts so the walk can
|
|
3805
|
+
* unwind (locks/teardown). Does not cancel sync busy-loops on the event loop.
|
|
3806
|
+
*/
|
|
3807
|
+
async function awaitUnlessAborted(work, signal) {
|
|
3808
|
+
if (!signal) {
|
|
3809
|
+
return await work;
|
|
3810
|
+
}
|
|
3811
|
+
signal.throwIfAborted();
|
|
3812
|
+
return new Promise((resolve, reject) => {
|
|
3813
|
+
const onAbort = () => {
|
|
3814
|
+
cleanup();
|
|
3815
|
+
reject(createStepWalkAbortError());
|
|
3816
|
+
};
|
|
3817
|
+
const cleanup = () => {
|
|
3818
|
+
signal.removeEventListener('abort', onAbort);
|
|
3819
|
+
};
|
|
3820
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
3821
|
+
Promise.resolve(work).then((value) => {
|
|
3822
|
+
cleanup();
|
|
3823
|
+
resolve(value);
|
|
3824
|
+
}, (error) => {
|
|
3825
|
+
cleanup();
|
|
3826
|
+
reject(error);
|
|
3827
|
+
});
|
|
3828
|
+
});
|
|
3829
|
+
}
|
|
3748
3830
|
async function runOptionalGitCommand(input) {
|
|
3749
3831
|
const { label, getCommand, cwd, logger } = input;
|
|
3750
3832
|
try {
|
|
@@ -3804,6 +3886,10 @@ async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables,
|
|
|
3804
3886
|
let runFailure;
|
|
3805
3887
|
try {
|
|
3806
3888
|
for (let i = 0; i < contextList.length; i++) {
|
|
3889
|
+
if (signal?.aborted) {
|
|
3890
|
+
runFailure = stepWalkAbortedFailure();
|
|
3891
|
+
break;
|
|
3892
|
+
}
|
|
3807
3893
|
const context = contextList[i];
|
|
3808
3894
|
logger.info(contextList.length > 1
|
|
3809
3895
|
? `Running context "${context.name}" (${i + 1}/${contextList.length})`
|
|
@@ -3816,127 +3902,137 @@ async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables,
|
|
|
3816
3902
|
const contextRunState = setupResult?.contextRunState || {};
|
|
3817
3903
|
let stepWalkFailure;
|
|
3818
3904
|
async function walkAndExecuteSteps(stepsToExec, currStepIndex) {
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
if (typeof step === 'function') {
|
|
3829
|
-
subSteps =
|
|
3905
|
+
try {
|
|
3906
|
+
for (let stepIndex = 0; stepIndex < stepsToExec.length; stepIndex++) {
|
|
3907
|
+
if (stepWalkFailure) {
|
|
3908
|
+
return;
|
|
3909
|
+
}
|
|
3910
|
+
signal?.throwIfAborted();
|
|
3911
|
+
const step = stepsToExec[stepIndex];
|
|
3912
|
+
const nextCallHeadIndex = [...currStepIndex, stepIndex];
|
|
3913
|
+
const compositeStepIndex = nextCallHeadIndex.length === 1 ? nextCallHeadIndex[0] : nextCallHeadIndex;
|
|
3914
|
+
if (typeof step === 'function' || Array.isArray(step)) {
|
|
3915
|
+
const subSteps = typeof step === 'function'
|
|
3916
|
+
? await awaitUnlessAborted(step({
|
|
3917
|
+
context,
|
|
3918
|
+
stepIndex: compositeStepIndex,
|
|
3919
|
+
contextRunState,
|
|
3920
|
+
lumpVariables,
|
|
3921
|
+
}), signal)
|
|
3922
|
+
: step;
|
|
3923
|
+
await walkAndExecuteSteps(subSteps, nextCallHeadIndex);
|
|
3924
|
+
continue;
|
|
3925
|
+
}
|
|
3926
|
+
logger.verbose(`step ${JSON.stringify(step)}`);
|
|
3927
|
+
const { commandFn = () => null, stepVariables, promptFn, postCommandExecFn, continueOnError, timeoutMillis = 1000 * 60 * 30, } = step;
|
|
3928
|
+
const prompt = promptFn
|
|
3929
|
+
? await awaitUnlessAborted(promptFn({
|
|
3830
3930
|
context,
|
|
3831
3931
|
stepIndex: compositeStepIndex,
|
|
3832
3932
|
contextRunState,
|
|
3833
3933
|
lumpVariables,
|
|
3934
|
+
stepVariables,
|
|
3935
|
+
}), signal)
|
|
3936
|
+
: '';
|
|
3937
|
+
const command = await awaitUnlessAborted(commandFn({
|
|
3938
|
+
context,
|
|
3939
|
+
prompt,
|
|
3940
|
+
stepIndex: compositeStepIndex,
|
|
3941
|
+
contextRunState,
|
|
3942
|
+
lumpVariables,
|
|
3943
|
+
stepVariables,
|
|
3944
|
+
projectRoot,
|
|
3945
|
+
workspacePath,
|
|
3946
|
+
}), signal);
|
|
3947
|
+
let commandResult = '';
|
|
3948
|
+
let commandSucceeded = true;
|
|
3949
|
+
if (command != null) {
|
|
3950
|
+
const { executable, args, env } = command;
|
|
3951
|
+
logger.verbose(`command for prompt ${executable} ${args.join(' ')}`);
|
|
3952
|
+
if (env != null) {
|
|
3953
|
+
logger.verbose(`command env overrides ${JSON.stringify(env)}`);
|
|
3954
|
+
}
|
|
3955
|
+
logger.verbose(`workspacePath ${workspacePath}`);
|
|
3956
|
+
const commandExec = await execBinary({
|
|
3957
|
+
binaryPath: executable,
|
|
3958
|
+
args,
|
|
3959
|
+
timeoutMillis,
|
|
3960
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
3961
|
+
cwd: workspacePath,
|
|
3962
|
+
signal,
|
|
3963
|
+
...(env != null ? { env: { ...process.env, ...env } } : {}),
|
|
3834
3964
|
});
|
|
3965
|
+
logger.verbose(`commandExec ${JSON.stringify(commandExec)}`);
|
|
3966
|
+
if (!commandExec.success) {
|
|
3967
|
+
const aborted = commandExec.data.reason === 'aborted';
|
|
3968
|
+
if (aborted || !continueOnError) {
|
|
3969
|
+
stepWalkFailure = {
|
|
3970
|
+
success: false,
|
|
3971
|
+
data: {
|
|
3972
|
+
message: `Failed to run the command: ${commandExec.data.message}. Command: ${executable} ${args.join(' ')}`,
|
|
3973
|
+
reason: 'stepWalkFailed',
|
|
3974
|
+
},
|
|
3975
|
+
};
|
|
3976
|
+
return;
|
|
3977
|
+
}
|
|
3978
|
+
commandSucceeded = false;
|
|
3979
|
+
commandResult = (commandExec.data.stdout
|
|
3980
|
+
|| commandExec.data.stderr
|
|
3981
|
+
|| commandExec.data.message
|
|
3982
|
+
|| '').toString();
|
|
3983
|
+
logger.verbose(`commandResult ${commandResult}`);
|
|
3984
|
+
}
|
|
3985
|
+
else {
|
|
3986
|
+
commandResult = (commandExec.data.stdout || commandExec.data.stderr || '').toString();
|
|
3987
|
+
logger.verbose(`commandResult ${commandResult}`);
|
|
3988
|
+
}
|
|
3989
|
+
if (commandSucceeded) {
|
|
3990
|
+
const gitStatusAfterCommand = await execAsync(`git status`, { cwd: workspacePath });
|
|
3991
|
+
logger.verbose(`gitStatusCommand ${JSON.stringify(gitStatusAfterCommand.data)}`);
|
|
3992
|
+
}
|
|
3835
3993
|
}
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
await walkAndExecuteSteps(subSteps, nextCallHeadIndex);
|
|
3840
|
-
continue;
|
|
3841
|
-
}
|
|
3842
|
-
logger.verbose(`step ${JSON.stringify(step)}`);
|
|
3843
|
-
const { commandFn = () => null, stepVariables, promptFn, postCommandExecFn, continueOnError, timeoutMillis = 1000 * 60 * 30, } = step;
|
|
3844
|
-
const prompt = promptFn
|
|
3845
|
-
? await promptFn({
|
|
3994
|
+
const historyEntry = {
|
|
3995
|
+
commandResult,
|
|
3996
|
+
commandSucceeded,
|
|
3846
3997
|
context,
|
|
3998
|
+
prompt,
|
|
3847
3999
|
stepIndex: compositeStepIndex,
|
|
3848
4000
|
contextRunState,
|
|
3849
4001
|
lumpVariables,
|
|
3850
4002
|
stepVariables,
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
prompt,
|
|
3856
|
-
stepIndex: compositeStepIndex,
|
|
3857
|
-
contextRunState,
|
|
3858
|
-
lumpVariables,
|
|
3859
|
-
stepVariables,
|
|
3860
|
-
projectRoot,
|
|
3861
|
-
workspacePath,
|
|
3862
|
-
});
|
|
3863
|
-
let commandResult = '';
|
|
3864
|
-
let commandSucceeded = true;
|
|
3865
|
-
if (command != null) {
|
|
3866
|
-
const { executable, args, env } = command;
|
|
3867
|
-
logger.verbose(`command for prompt ${executable} ${args.join(' ')}`);
|
|
3868
|
-
if (env != null) {
|
|
3869
|
-
logger.verbose(`command env overrides ${JSON.stringify(env)}`);
|
|
3870
|
-
}
|
|
3871
|
-
logger.verbose(`workspacePath ${workspacePath}`);
|
|
3872
|
-
const commandExec = await execBinary({
|
|
3873
|
-
binaryPath: executable,
|
|
3874
|
-
args,
|
|
3875
|
-
timeoutMillis,
|
|
3876
|
-
stdio: ['inherit', 'pipe', 'pipe'],
|
|
3877
|
-
cwd: workspacePath,
|
|
4003
|
+
projectRoot,
|
|
4004
|
+
};
|
|
4005
|
+
const postCommandExecFnInput = {
|
|
4006
|
+
...historyEntry,
|
|
3878
4007
|
signal,
|
|
3879
|
-
|
|
3880
|
-
});
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
};
|
|
3892
|
-
return;
|
|
4008
|
+
};
|
|
4009
|
+
logger.verbose(`context is ${JSON.stringify(context)}`);
|
|
4010
|
+
const keepHistoryFilePath = getKeepHistoryFilePathFn(context) || '';
|
|
4011
|
+
logger.verbose(`keepHistoryFilePath ${keepHistoryFilePath}`);
|
|
4012
|
+
if (!!command && keepHistoryFilePath.length > 0) {
|
|
4013
|
+
const appendResult = await appendHistoryEntry({
|
|
4014
|
+
filePath: keepHistoryFilePath,
|
|
4015
|
+
entry: historyEntry,
|
|
4016
|
+
});
|
|
4017
|
+
if (!appendResult.success) {
|
|
4018
|
+
// TODO: sanitize history appending to avoid this warning for certain commands outputs
|
|
4019
|
+
logger.warn(`Failed to append history entry to ${keepHistoryFilePath}: ${appendResult.data}`);
|
|
3893
4020
|
}
|
|
3894
|
-
commandSucceeded = false;
|
|
3895
|
-
commandResult = (commandExec.data.stdout
|
|
3896
|
-
|| commandExec.data.stderr
|
|
3897
|
-
|| commandExec.data.message
|
|
3898
|
-
|| '').toString();
|
|
3899
|
-
logger.verbose(`commandResult ${commandResult}`);
|
|
3900
|
-
}
|
|
3901
|
-
else {
|
|
3902
|
-
commandResult = (commandExec.data.stdout || commandExec.data.stderr || '').toString();
|
|
3903
|
-
logger.verbose(`commandResult ${commandResult}`);
|
|
3904
|
-
}
|
|
3905
|
-
if (commandSucceeded) {
|
|
3906
|
-
const gitStatusAfterCommand = await execAsync(`git status`, { cwd: workspacePath });
|
|
3907
|
-
logger.verbose(`gitStatusCommand ${JSON.stringify(gitStatusAfterCommand.data)}`);
|
|
3908
4021
|
}
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
prompt,
|
|
3915
|
-
stepIndex: compositeStepIndex,
|
|
3916
|
-
contextRunState,
|
|
3917
|
-
lumpVariables,
|
|
3918
|
-
stepVariables,
|
|
3919
|
-
projectRoot,
|
|
3920
|
-
};
|
|
3921
|
-
logger.verbose(`context is ${JSON.stringify(context)}`);
|
|
3922
|
-
const keepHistoryFilePath = getKeepHistoryFilePathFn(context) || '';
|
|
3923
|
-
logger.verbose(`keepHistoryFilePath ${keepHistoryFilePath}`);
|
|
3924
|
-
if (!!command && keepHistoryFilePath.length > 0) {
|
|
3925
|
-
const appendResult = await appendHistoryEntry({
|
|
3926
|
-
filePath: keepHistoryFilePath,
|
|
3927
|
-
entry: postCommandExecFnInput,
|
|
3928
|
-
});
|
|
3929
|
-
if (!appendResult.success) {
|
|
3930
|
-
// TODO: sanitize history appending to avoid this warning for certain commands outputs
|
|
3931
|
-
logger.warn(`Failed to append history entry to ${keepHistoryFilePath}: ${appendResult.data}`);
|
|
4022
|
+
if (postCommandExecFn) {
|
|
4023
|
+
const returnedSteps = await awaitUnlessAborted(postCommandExecFn(postCommandExecFnInput), signal);
|
|
4024
|
+
if (returnedSteps != null && returnedSteps.length > 0) {
|
|
4025
|
+
await walkAndExecuteSteps(returnedSteps, nextCallHeadIndex);
|
|
4026
|
+
}
|
|
3932
4027
|
}
|
|
3933
4028
|
}
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
4029
|
+
}
|
|
4030
|
+
catch (error) {
|
|
4031
|
+
if (isStepWalkAbortError(error)) {
|
|
4032
|
+
stepWalkFailure = stepWalkAbortedFailure();
|
|
4033
|
+
return;
|
|
3939
4034
|
}
|
|
4035
|
+
throw error;
|
|
3940
4036
|
}
|
|
3941
4037
|
}
|
|
3942
4038
|
try {
|
|
@@ -4138,13 +4234,13 @@ async function getContextStatus(params) {
|
|
|
4138
4234
|
if (!fetchResult.success)
|
|
4139
4235
|
return 'toDo';
|
|
4140
4236
|
}
|
|
4141
|
-
const logResult = await execAsync(`git log --remotes=${remoteName} -F --grep=${shellSingleQuote(commitMessage)} --format=${shellSingleQuote(
|
|
4237
|
+
const logResult = await execAsync(`git log --remotes=${remoteName} -F --grep=${shellSingleQuote(commitMessage)} --format=${shellSingleQuote(GIT_LOG_HASH_BODY_FORMAT)}`, { cwd: projectRoot });
|
|
4142
4238
|
if (!logResult.success)
|
|
4143
4239
|
return 'toDo';
|
|
4144
4240
|
logger?.verbose(`logResult ${JSON.stringify(logResult.data)}`);
|
|
4145
4241
|
const logResultOutput = logResult.data.stdout || logResult.data.stderr || '';
|
|
4146
|
-
const matchingHashes =
|
|
4147
|
-
.filter((entry) => entry.
|
|
4242
|
+
const matchingHashes = parseGitLogHashBodyRecords(logResultOutput)
|
|
4243
|
+
.filter((entry) => commitMessageIncludesMarker(entry.message, commitMessage))
|
|
4148
4244
|
.map((entry) => entry.hash);
|
|
4149
4245
|
logger?.verbose(`contextName ${contextName}`);
|
|
4150
4246
|
logger?.verbose(`remoteName ${remoteName}`);
|
|
@@ -4363,5 +4459,5 @@ async function runLump(input) {
|
|
|
4363
4459
|
});
|
|
4364
4460
|
}
|
|
4365
4461
|
|
|
4366
|
-
export { appendHistoryEntry, collectStepsForContext, contextStatus, createConsoleLogger, defaultGitAddCommandFn, defaultGitCommitCommandFn, defaultGitCommitMessageFn, defaultGitPushCommandFn, defaultSetupWorkspaceFn, defaultSetupWorkspaceFnWithWorktree, defaultTeardownWorkspaceFn, defaultTeardownWorkspaceFnWithWorktree, execAsync, execBinary, executeStepsForContextList, failure, formatExecFailureMessage, getCodeBasePaths, getContextStatus, getToDoContextList, historyFormatFromPath, isProcessAlive, killProcessTree, nodeErrnoCode, parseGitLogHashSubjectLines, pathExists, readHistoryFile, refreshRemoteTrackingRefs, resolveSpawnExecutable, runLump, set, shellSingleQuote, success, validateContextListNames, windowsNpmCmdShimBody, writeHistoryFile };
|
|
4462
|
+
export { GIT_LOG_HASH_BODY_FORMAT, appendHistoryEntry, collectStepsForContext, commitMessageIncludesMarker, contextStatus, createConsoleLogger, defaultGitAddCommandFn, defaultGitCommitCommandFn, defaultGitCommitMessageFn, defaultGitPushCommandFn, defaultSetupWorkspaceFn, defaultSetupWorkspaceFnWithWorktree, defaultTeardownWorkspaceFn, defaultTeardownWorkspaceFnWithWorktree, execAsync, execBinary, executeStepsForContextList, failure, formatExecFailureMessage, getCodeBasePaths, getContextStatus, getToDoContextList, historyFormatFromPath, isProcessAlive, killProcessTree, nodeErrnoCode, parseGitLogHashBodyRecords, parseGitLogHashSubjectLines, pathExists, readHistoryFile, refreshRemoteTrackingRefs, resolveSpawnExecutable, runLump, set, shellSingleQuote, success, validateContextListNames, windowsNpmCmdShimBody, writeHistoryFile };
|
|
4367
4463
|
//# sourceMappingURL=index.js.map
|