The File Cabinet Has No Version Control, and It Does Not Tell You

MokuHub7 min read
netsuitesuitescriptfile-cabinetsuiteql

Two people open the same SuiteScript file in the File Cabinet on a Tuesday morning. One of them saves at 10:14. The other saves at 10:31.

The 10:14 change is gone. Not conflicted, not flagged, not recoverable from a history tab. Gone, and neither developer will find out until something breaks in production a week later and the fix that was definitely applied turns out not to be in the file.

This is not a bug. The File Cabinet was built as a place to store documents, and it stores your source code the same way it stores a PDF of last quarter's invoices. Documents do not usually need three-way merges. Source code does.

What the File Cabinet actually gives you

Open a .js file in the File Cabinet and click Edit. You get a textarea. Not an editor with a textarea in it. A textarea, with the browser's own undo stack and nothing else.

What you do not get:

  • Any record of who last changed the file, beyond a single last-modified timestamp
  • Any previous version of the contents
  • Any lock, advisory or otherwise, while someone else has the file open
  • Any warning at save time that the file on the server is not the file you started from

That last one is the dangerous one. The other three are inconveniences you can work around with discipline. Silent overwrite cannot be worked around with discipline, because by definition nobody involved knows it happened.

Why "just use SDF" is a real answer and an insufficient one

The correct answer, and the one every NetSuite consultant will give you, is to keep your scripts in git and deploy them through the SuiteCloud Development Framework. That is right. It is how a team should work, and if you are not doing it, that is the project to start.

It is also not what happens at 4pm on a Friday when a scheduled script is throwing an error in production and the fix is two characters.

The gap between the process a team has documented and the process a team uses under pressure is where these overwrites live. Everyone has SDF configured. Everyone still occasionally edits the file in the browser, because the round trip through a local checkout, an account-specific deployment and a validation pass takes longer than the fix does. The people most likely to do it are the senior developers, because they are the ones who get called when production is broken.

So you have two realities. In one, files move through version control. In the other, they get edited in place. The second reality is the one with no safety net at all, and pretending it does not exist has not made it stop.

The one thing NetSuite does give you

There is exactly one primitive available, and it is enough to build a warning on: the file's last-modified timestamp, readable through SuiteQL.

SELECT id, name, folder, lastmodifieddate
FROM file
WHERE id = 12345

That returns the server's opinion of when the file last changed. Which means you can implement optimistic concurrency control by hand, the same pattern every database has used since the 1980s:

  1. When you open the file, record its contents and its lastmodifieddate. Call that pair your baseline.
  2. Edit locally, against the baseline.
  3. Before you write, query lastmodifieddate again.
  4. If it moved, someone else saved while you were working. Stop and look before you write.

Four steps. No infrastructure. The interesting part is what you do at step four, and it is not "abort".

Aborting is the wrong default

The obvious implementation refuses the save and tells the user to reload. This is technically correct and practically useless, because reloading throws away the work the user just did. Faced with that dialogue twice, any developer will start copying their changes to a scratch file first, and now you have a worse version of the problem with an extra manual step in it.

What you want at step four is the three-way situation laid out explicitly:

  • The baseline: the file as it was when you opened it.
  • Theirs: the file as it is on the server now.
  • Yours: the file with your edits.

With those three you can compute an actual merge rather than picking a winner. Most of the time the two sets of changes are in different functions and the merge is mechanical. The rest of the time you want a human to look at two specific conflicting hunks, not at two entire files.

The important design decision is what "discard" means. Discarding your local changes must restore the file from the server, not from whatever you happened to have cached. If it restores from cache, you have quietly reintroduced the exact bug you were trying to prevent, one layer up. Discard means "give me the truth", and the truth lives on the server.

The failure mode nobody plans for

Everything above assumes two humans. There is a third writer, and it is the one that catches teams out: bundle and SuiteApp installs.

When a bundle updates, it overwrites the files it owns. If someone has customised one of those files in place, which happens constantly with bundled scripts that were almost right, the customisation disappears on the next bundle update. There is no conflict, because bundles do not merge. There is no warning, because the bundle does not know your edit was deliberate.

The baseline check catches this too, and it is arguably the case where it matters most, because bundle updates happen on someone else's schedule. If your tooling records a baseline when a file is opened and re-checks before every write, an unexpected lastmodifieddate is an unexpected lastmodifieddate whether a colleague or an installer moved it.

The practical rule that follows: never customise a bundled file in place. Copy it, rename it, deploy the copy, and let the bundle own its original. If you have inherited an account where this rule was not followed, a SuiteQL query over the file table sorted by lastmodifieddate immediately after a bundle update will tell you what just changed under you.

What this costs you today

Take an honest inventory of your own account. How many scripts are deployed from files that exist only in the File Cabinet, with no copy in a repository anywhere? In most accounts that have been live for more than three years the answer is "more than we would like to say out loud", and the files in that category are usually the oldest and least understood ones.

For those files there is no history to consult. The current contents are the only documentation of what the script does, and the only record of every decision made about it. If two people overwrite each other on one of them, nothing anywhere records that a decision was reversed.

That is the real cost, and it is not the lost hour of typing. It is that the account slowly accumulates code whose history is unknowable, in a system where the code is the business logic that moves money.

Where this stops working

The lastmodifieddate check has a race in it, and you should know where.

Between your check and your write there is a window. If someone saves inside that window, you overwrite them anyway. The window is small, usually well under a second, and in practice a two-developer team will not hit it. A CI process writing files on a schedule absolutely can.

There is no fix for this inside NetSuite. There is no conditional write, no compare-and-swap, no ETag on the save. You cannot close the window, only make it narrow: re-check immediately before the write rather than at the start of a save routine, and do not batch a check for twenty files at the top of a loop that then writes twenty files.

The honest summary is that this is a warning system, not a lock. It converts a silent data loss into a visible conflict almost every time. Almost.

For a class of problem that currently has no detection at all, almost every time is a large improvement, and it is available to anyone willing to run one extra query before each save. There is nothing proprietary about the pattern. It is four steps and one SuiteQL query, and you can build it into whatever tooling you already have.

We built it into our own, because we wanted it ourselves and because an editor that runs on the File Cabinet has no business writing to a file it has not re-checked. If you want the longer version of that, MokuStudio is written up here: what it is, what it resolves that no other File Cabinet editor does, and the parts it deliberately does not do.

The version control problem in the File Cabinet is not that NetSuite lacks a merge tool. It is that a system with no concurrency control at all is treated by everyone who uses it as though it has some.