From 4681ce4fbad5848e459076d485a6b272fa58348c Mon Sep 17 00:00:00 2001 From: Thomas Taylor Date: Tue, 21 Apr 2026 20:53:08 +0100 Subject: [PATCH 1/2] New: Headless --dev install driven by repos.json (fixes #97) --- bin/install.js | 14 +++++++++++--- lib/Utils.js | 2 ++ lib/utils/cloneRepo.js | 4 ++-- lib/utils/installLocalModules.js | 9 +++++++-- lib/utils/loadDevModulesConfig.js | 18 ++++++++++++++++++ 5 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 lib/utils/loadDevModulesConfig.js diff --git a/bin/install.js b/bin/install.js index 1b76823..5e7d19b 100644 --- a/bin/install.js +++ b/bin/install.js @@ -26,14 +26,12 @@ export default class Install extends CliCommand { return new UiServer(this.options) .on('exit', e => this.cleanUp(e)) } - if (this.options.devMode) { - console.log('IMPORTANT: dev mode flag currently has no effect when running in headless mode\n') - } try { if (!this.options.tag) this.options.tag = await this.getReleaseInput() console.log(`Installing Adapt authoring tool ${this.options.tag} in ${this.options.cwd}`) await this.cloneRepo() + if (this.options.devMode) await this.installDevModules() await Utils.registerSuperUser(this.options) await Utils.clearInstallState(this.options.cwd) @@ -43,6 +41,16 @@ export default class Install extends CliCommand { } } + async installDevModules () { + const modules = await Utils.loadDevModulesConfig(this.options.cwd) + if (!modules?.length) { + console.log('Dev mode: no repos.json found in install root, skipping local modules') + return + } + console.log(`Dev mode: cloning ${modules.length} local modules from repos.json`) + await Utils.installLocalModules({ ...this.options, modules }) + } + async handleExistingInstall () { const checkpoint = await Utils.getInstallState(this.options.cwd) if (checkpoint) { diff --git a/lib/Utils.js b/lib/Utils.js index 85ad5fd..e899e13 100644 --- a/lib/Utils.js +++ b/lib/Utils.js @@ -14,6 +14,7 @@ import importCore from './utils/importCore.js' import { getInstallState, saveInstallState, clearInstallState } from './utils/installState.js' import installLocalModules from './utils/installLocalModules.js' import isModule from './utils/isModule.js' +import loadDevModulesConfig from './utils/loadDevModulesConfig.js' import loadJson from './utils/loadJson.js' import loadPackage from './utils/loadPackage.js' import startApp from './utils/startApp.js' @@ -41,6 +42,7 @@ export default { importCore, installLocalModules, isModule, + loadDevModulesConfig, loadJson, loadPackage, parseBody, diff --git a/lib/utils/cloneRepo.js b/lib/utils/cloneRepo.js index f3d7cce..98196b3 100644 --- a/lib/utils/cloneRepo.js +++ b/lib/utils/cloneRepo.js @@ -4,7 +4,7 @@ const GITHUB_ORG_URL = 'https://github.com/adapt-security' const GITHUB_REPO = 'adapt-authoring' export default async function cloneRepo (options) { - const url = `${GITHUB_ORG_URL}/${options.repo || GITHUB_REPO}.git` + const url = options.url || `${GITHUB_ORG_URL}/${options.repo || GITHUB_REPO}.git` const tag = options.tag || 'master' console.log(`Cloning ${url}#${tag} into ${options.cwd}`) try { @@ -16,4 +16,4 @@ export default async function cloneRepo (options) { } console.log('Installing npm dependencies') await exec(`npm ${options.cleanInstall === false ? 'install' : 'ci'}`, options.cwd) -} +} diff --git a/lib/utils/installLocalModules.js b/lib/utils/installLocalModules.js index 6d44df1..2962d54 100644 --- a/lib/utils/installLocalModules.js +++ b/lib/utils/installLocalModules.js @@ -4,6 +4,11 @@ import path from 'path' export default async function installLocalModules (options) { const dir = path.join(options.cwd, 'local_adapt_modules') - await Promise.all(options.modules.map(m => cloneRepo({ repo: m, cwd: path.join(dir, m), cleanInstall: false }))) + await Promise.all(options.modules.map(m => { + const name = typeof m === 'string' ? m : m.name + const url = typeof m === 'string' ? undefined : m.url + const localDir = name.includes('/') ? name.split('/')[1] : name + return cloneRepo({ repo: name, url, cwd: path.join(dir, localDir), cleanInstall: false }) + })) return exec('npm install', options.cwd) // need to run another npm install to pull in the local repos -} +} diff --git a/lib/utils/loadDevModulesConfig.js b/lib/utils/loadDevModulesConfig.js new file mode 100644 index 0000000..babe086 --- /dev/null +++ b/lib/utils/loadDevModulesConfig.js @@ -0,0 +1,18 @@ +import fs from 'fs/promises' +import path from 'path' + +/** + * Looks for a repos.json config file in the umbrella install root. Used by + * headless dev installs to determine which workspace modules to clone and + * where to clone them from. Returns null if the file isn't present, so the + * interactive/discovery flow can still run. + */ +export default async function loadDevModulesConfig (cwd) { + const configPath = path.resolve(cwd, 'repos.json') + try { + return JSON.parse(await fs.readFile(configPath, 'utf8')) + } catch (e) { + if (e.code === 'ENOENT') return null + throw e + } +} From ecc906eadcb5d4a3a3daba80d15b5e608399395c Mon Sep 17 00:00:00 2001 From: Thomas Taylor Date: Wed, 22 Apr 2026 11:59:24 +0100 Subject: [PATCH 2/2] Build: Drop lockfile requirement from tests workflow --- .github/workflows/tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b28cd6f..d1d0778 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,6 +10,5 @@ jobs: - uses: actions/setup-node@master with: node-version: 'lts/*' - cache: 'npm' - - run: npm ci + - run: npm install - run: npm test