Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
e68b299d0This file is pinned to the selected release. Download the original to open it in your design tool.
#!/usr/bin/env node
import { spawnSync } from 'node:child_process'
import { existsSync, statSync } from 'node:fs'
import path from 'node:path'
import {
assertNoCustomBuildOutput,
buildOutputDirectory,
ensureBuildOutputDirectory,
hostApplicationName,
moddableOutputArguments,
} from './lib/build-output.mjs'
import { aliases, devices, resolveDevice } from './lib/devices.mjs'
import { prepareCoreS3IdfDependencies } from './lib/idf-dependencies.mjs'
const command = process.argv[2]
const rawArgs = process.argv.slice(3)
const targetOption = readOption(rawArgs, 'target')
try {
assertNoCustomBuildOutput(rawArgs)
} catch (error) {
console.error(`[stack-chan] ${error.message}`)
process.exit(1)
}
if (!command || command === 'help' || command === '--help' || command === '-h') {
printHelp()
process.exit(command ? 0 : 1)
}
if (targetOption) {
console.error(`[stack-chan] --target is not supported by this command wrapper: ${targetOption}`)
console.error('[stack-chan] Use a named script such as npm run flash:stackchan_rt or npm run flash:takao_core2_sg90.')
process.exit(1)
}
// npm swallows `--target=...` into this env var instead of argv; without this
// guard the wrapper would silently fall back to the default device.
if (process.env.npm_config_target) {
console.error(`[stack-chan] --target is not supported by this command wrapper: ${process.env.npm_config_target}`)
console.error(
'[stack-chan] Use a named script such as npm run build:m5stack, npm run build:m5stackchan_cores3, or npm run flash:takao_core2_sg90.',
)
process.exit(1)
}
const deviceName = resolveDevice(
readOption(rawArgs, 'device') ?? process.env.STACKCHAN_DEVICE ?? firstDeviceArg(rawArgs),
)
const device = devices[deviceName]
const args = positionalArgs(rawArgs).filter((arg) => !isDeviceName(arg) && !isBuildModeFlag(arg))
const platform = `esp32:${device.platform}`
const manifest = readOption(rawArgs, 'manifest') ?? process.env.STACKCHAN_MANIFEST ?? device.manifest
const dryRun = process.env.STACKCHAN_DRY_RUN === '1'
const { mode: buildMode, args: buildModeArgs } = readBuildConfiguration(rawArgs)
const outputArgs = moddableOutputArguments()
if (!dryRun && deviceName === 'm5stackchan_cores3' && command !== 'mod') {
try {
prepareCoreS3IdfDependencies({
outputDirectory: buildOutputDirectory,
platformName: deviceName,
applicationName: hostApplicationName,
mode: buildMode,
})
} catch (error) {
console.error(`[stack-chan] IDF dependencies could not be prepared: ${error.message}`)
process.exit(1)
}
}
switch (command) {
case 'build':
run('mcconfig', [
...buildModeArgs,
'-m',
'-p',
platform,
'-t',
'build',
...outputArgs,
path.resolve(manifest),
...args,
])
break
case 'flash':
run('mcconfig', [...buildModeArgs, '-m', '-p', platform, ...outputArgs, path.resolve(manifest), ...args])
break
case 'deploy':
run('mcconfig', [
...buildModeArgs,
'-m',
'-p',
platform,
'-t',
'deploy',
...outputArgs,
path.resolve(manifest),
...args,
])
break
case 'debug':
run('mcconfig', [...buildModeArgs, '-m', '-p', platform, ...outputArgs, path.resolve(manifest), ...args])
break
case 'mod': {
const modInput = args[0]
if (!modInput) {
console.error(
'[stack-chan] MOD manifestを指定してください: npm run mod -- mods/examples/look_around/manifest.json',
)
process.exit(1)
}
const packageDirectory = findPackageDirectory(modInput)
if (packageDirectory) {
run('mcpack', ['mcrun', '-d', '-m', '-p', platform, ...outputArgs, ...args.slice(1)], packageDirectory)
} else {
run('mcrun', ['-d', '-m', '-p', platform, ...outputArgs, modInput, ...args.slice(1)])
}
break
}
default:
console.error(`[stack-chan] Unknown command: ${command}`)
printHelp()
process.exit(1)
}
/**
* Runs a firmware tool or prints the command during a dry run.
* @param {string} bin - Executable name.
* @param {string[]} binArgs - Command-line arguments.
* @param {string} cwd - Working directory for the subprocess.
*/
function run(bin, binArgs, cwd = process.cwd()) {
console.log(`[stack-chan] ${command}: ${device.label}`)
console.log(`[stack-chan] platform=${platform}`)
if (command !== 'mod') console.log(`[stack-chan] manifest=${manifest}`)
console.log(`[stack-chan] output=${buildOutputDirectory}`)
if (cwd !== process.cwd()) console.log(`[stack-chan] cwd=${cwd}`)
if (dryRun) {
console.log([bin, ...binArgs].join(' '))
return
}
ensureBuildOutputDirectory()
const result = spawnSync(bin, binArgs, { cwd, stdio: 'inherit' })
if (result.error) {
console.error(`[stack-chan] ${bin}を実行できませんでした: ${result.error.message}`)
console.error('[stack-chan] npm run setup と npm run doctor を確認してください。')
process.exit(1)
}
process.exit(result.status ?? 1)
}
/**
* Finds the package directory represented by a path, if any.
* @param {string} value - Package directory, package.json, or manifest path.
* @returns {string|null} Resolved package directory.
*/
function findPackageDirectory(value) {
const resolved = path.resolve(value)
if (!existsSync(resolved)) return null
if (statSync(resolved).isDirectory()) {
return existsSync(path.join(resolved, 'package.json')) ? resolved : null
}
if (path.basename(resolved) === 'package.json') return path.dirname(resolved)
return null
}
/**
* Reads a long option supplied as either --name value or --name=value.
* @param {string[]} values - Command-line arguments.
* @param {string} name - Option name without leading dashes.
* @returns {string|undefined} Option value.
*/
function readOption(values, name) {
const prefix = `--${name}=`
const index = values.indexOf(`--${name}`)
if (index >= 0) return values[index + 1]
return values.find((value) => value.startsWith(prefix))?.slice(prefix.length)
}
/**
* Resolves the Moddable output mode and selector arguments for this invocation.
* @param {string[]} values - Command-line arguments.
* @returns {{mode: string, args: string[]}} Build directory mode and mcconfig selector arguments.
*/
function readBuildConfiguration(values) {
const mode = readOption(values, 'mode') ?? process.env.STACKCHAN_BUILD_MODE
if (mode) {
if (mode === 'debug') return { mode, args: [values.find(isDebugBuildFlag) ?? '-d'] }
if (mode === 'instrument') return { mode, args: ['-i'] }
if (mode === 'release') return { mode, args: [] }
console.error(`[stack-chan] Unsupported build mode: ${mode}`)
console.error('[stack-chan] Use --mode=debug, --mode=instrument, or --mode=release.')
process.exit(1)
}
const debugFlag = values.find(isDebugBuildFlag)
if (debugFlag) return { mode: 'debug', args: [debugFlag] }
if (values.includes('-i')) return { mode: 'instrument', args: ['-i'] }
return { mode: 'debug', args: ['-d'] }
}
/**
* Returns positional and short-form arguments while removing long options.
* @param {string[]} values - Command-line arguments.
* @returns {string[]} Remaining arguments.
*/
function positionalArgs(values) {
const result = []
for (let index = 0; index < values.length; index += 1) {
const value = values[index]
if (value.startsWith('--')) {
if (!value.includes('=')) index += 1
continue
}
result.push(value)
}
return result
}
/**
* Finds the first named device argument or selects the default device.
* @param {string[]} values - Command-line arguments.
* @returns {string} Resolved device name candidate.
*/
function firstDeviceArg(values) {
return positionalArgs(values).find(isDeviceName) ?? 'm5stackchan_cores3'
}
/**
* Tests whether a value names a supported device or alias.
* @param {string} value - Candidate device name.
* @returns {boolean} Whether the value is a known device name.
*/
function isDeviceName(value) {
return Boolean(value && (devices[value] || aliases[value]))
}
/**
* Tests whether an argument selects a Moddable output mode.
* @param {string} value - Command-line argument.
* @returns {boolean} Whether the argument selects a build mode.
*/
function isBuildModeFlag(value) {
return value === '-i' || isDebugBuildFlag(value)
}
/**
* Tests whether an argument selects a debug build and debugger behavior.
* @param {string} value - Command-line argument.
* @returns {boolean} Whether the argument is a debug selector.
*/
function isDebugBuildFlag(value) {
return ['-d', '-dn', '-dx', '-dl'].includes(value)
}
/**
* Prints command usage and supported devices.
*/
function printHelp() {
console.log(`Usage:
npm run build
npm run flash
npm run debug
npm run mod -- <mod-manifest>
Devices:
m5stackchan_cores3 M5StackChan CoreS3 (default)
stackchan_rt Stack-chan RT CoreS3
takao_core2_sg90 Stack-chan Takao Core2 + SG90
Examples:
npm run flash:stackchan_rt
npm run build:takao_core2_sg90
npm run build:m5stackchan_cores3 -- --mode=release
npm run build:m5stackchan_cores3 -- --mode=instrument
STACKCHAN_DEVICE=takao_core2_sg90 npm run mod -- mods/examples/look_around/manifest.json`)
}