Run scripts and keep data
Your app's command-line scripts, the data they import and your design files go in the custom/ folder at the root. A script that talks to Firebase gets the project, the install's database and the credentials from runAdminScript, so it never handles credentials itself.
The custom folder
custom/ at the root of the repository belongs to your app. Arc CMS ships it with only a README and never changes it, and npm run check:core counts everything in it as custom. Nothing in it is bundled into the site. A suggested layout, none of it required:
| Folder | For |
|---|---|
custom/scripts/ | Scripts you run from the command line, such as data imports and one-off fixes |
custom/data/ | Source files those scripts read, such as CSV or JSON exports |
custom/design/ | Design notes, mockups and other working files |
Scripts that need packages of their own can have a custom/package.json: run npm install in custom/. Git ignores node_modules at any depth, so its packages are never committed.
The app's developer documentation goes in docs/custom/, next to these docs. Code that runs in the site or in Cloud Functions goes in src/custom/ and functions/src/custom/ (Use the custom space).
Write a script
-
Create the script, such as
custom/scripts/import-words.mjs, and hand your code torunAdminScriptfromscripts/arc-admin-script.mjs:import { readFileSync } from 'node:fs'; import { runAdminScript } from '../../scripts/arc-admin-script.mjs'; runAdminScript(async ({ db, args }) => { const words = JSON.parse(readFileSync(args[0] ?? 'custom/data/words.json', 'utf8')); const batch = db.batch(); for (const word of words) batch.set(db.collection('words').doc(word.id), word); await batch.commit(); console.log(`Imported ${words.length} words`); }); -
Sign in to Firebase once, if you have not:
firebase login -
Run it from the root of the repository. Before your code runs, it prints the project, the database and the credentials it uses:
node custom/scripts/import-words.mjs custom/data/words.json
Which project
| Run with | Project |
|---|---|
| No flag | The default alias in .firebaserc (the dev project), or GCLOUD_PROJECT when it is set |
--prod | The production alias |
--project=ALIAS_OR_ID | Any alias in .firebaserc, or a project id |
These flags are taken out; every other argument reaches your script in args, in order.
What your script gets
| Name | What it is |
|---|---|
db | Firestore for the install's own database: the one in ARC_DATABASE_ID, read from functions/.env and functions/.env.PROJECT_ID the way a deploy reads them |
auth | Firebase Authentication, to read or change users |
storage | Cloud Storage. storage.bucket() is the bucket your environment file names for this project |
app | The Admin SDK app behind them |
projectId, databaseId | Where the script runs |
prod | Whether it runs against the production alias, for a script that should ask before changing live data |
args | The command-line arguments left after the flags above |
Use db rather than calling getFirestore() yourself. A project can hold more than one Arc CMS database (Share a Firebase project), and getFirestore() always opens (default), which may belong to another install.
Credentials
When GOOGLE_APPLICATION_CREDENTIALS is set, such as to a service account key, the script signs in with it. Otherwise it uses your Firebase CLI login through a temporary credentials file, which it deletes when the script ends, fails or is stopped with Ctrl+C. With neither, it stops and says to run firebase login.
When your code throws, the error is printed and the script exits with 1. npm run seed:dev and npm run forms:remove-referrals run the same way.
Test a script
Files named custom/**/*.spec.ts run with the rest of the suite in npm run test. Keep the logic of a script in a function you can import, and test that function with sample data rather than a real project.