@base44-preview/cli 0.1.22-pr.644.4a848b7 → 0.1.22-pr.644.52a9e2d

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.
@@ -1221,7 +1221,7 @@
1221
1221
  "import { lstat, readdir, realpath, stat } from 'node:fs/promises';\nimport { join as pjoin, relative as prelative, resolve as presolve, sep as psep } from 'node:path';\nimport { Readable } from 'node:stream';\nexport const EntryTypes = {\n FILE_TYPE: 'files',\n DIR_TYPE: 'directories',\n FILE_DIR_TYPE: 'files_directories',\n EVERYTHING_TYPE: 'all',\n};\nconst defaultOptions = {\n root: '.',\n fileFilter: (_entryInfo) => true,\n directoryFilter: (_entryInfo) => true,\n type: EntryTypes.FILE_TYPE,\n lstat: false,\n depth: 2147483648,\n alwaysStat: false,\n highWaterMark: 4096,\n};\nObject.freeze(defaultOptions);\nconst RECURSIVE_ERROR_CODE = 'READDIRP_RECURSIVE_ERROR';\nconst NORMAL_FLOW_ERRORS = new Set(['ENOENT', 'EPERM', 'EACCES', 'ELOOP', RECURSIVE_ERROR_CODE]);\nconst ALL_TYPES = [\n EntryTypes.DIR_TYPE,\n EntryTypes.EVERYTHING_TYPE,\n EntryTypes.FILE_DIR_TYPE,\n EntryTypes.FILE_TYPE,\n];\nconst DIR_TYPES = new Set([\n EntryTypes.DIR_TYPE,\n EntryTypes.EVERYTHING_TYPE,\n EntryTypes.FILE_DIR_TYPE,\n]);\nconst FILE_TYPES = new Set([\n EntryTypes.EVERYTHING_TYPE,\n EntryTypes.FILE_DIR_TYPE,\n EntryTypes.FILE_TYPE,\n]);\nconst isNormalFlowError = (error) => NORMAL_FLOW_ERRORS.has(error.code);\nconst wantBigintFsStats = process.platform === 'win32';\nconst emptyFn = (_entryInfo) => true;\nconst normalizeFilter = (filter) => {\n if (filter === undefined)\n return emptyFn;\n if (typeof filter === 'function')\n return filter;\n if (typeof filter === 'string') {\n const fl = filter.trim();\n return (entry) => entry.basename === fl;\n }\n if (Array.isArray(filter)) {\n const trItems = filter.map((item) => item.trim());\n return (entry) => trItems.some((f) => entry.basename === f);\n }\n return emptyFn;\n};\n/** Readable readdir stream, emitting new files as they're being listed. */\nexport class ReaddirpStream extends Readable {\n parents;\n reading;\n parent;\n _stat;\n _maxDepth;\n _wantsDir;\n _wantsFile;\n _wantsEverything;\n _root;\n _isDirent;\n _statsProp;\n _rdOptions;\n _fileFilter;\n _directoryFilter;\n constructor(options = {}) {\n super({\n objectMode: true,\n autoDestroy: true,\n highWaterMark: options.highWaterMark,\n });\n const opts = { ...defaultOptions, ...options };\n const { root, type } = opts;\n this._fileFilter = normalizeFilter(opts.fileFilter);\n this._directoryFilter = normalizeFilter(opts.directoryFilter);\n const statMethod = opts.lstat ? lstat : stat;\n // Use bigint stats if it's windows and stat() supports options (node 10+).\n if (wantBigintFsStats) {\n this._stat = (path) => statMethod(path, { bigint: true });\n }\n else {\n this._stat = statMethod;\n }\n this._maxDepth =\n opts.depth != null && Number.isSafeInteger(opts.depth) ? opts.depth : defaultOptions.depth;\n this._wantsDir = type ? DIR_TYPES.has(type) : false;\n this._wantsFile = type ? FILE_TYPES.has(type) : false;\n this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;\n this._root = presolve(root);\n this._isDirent = !opts.alwaysStat;\n this._statsProp = this._isDirent ? 'dirent' : 'stats';\n this._rdOptions = { encoding: 'utf8', withFileTypes: this._isDirent };\n // Launch stream with one parent, the root dir.\n this.parents = [this._exploreDir(root, 1)];\n this.reading = false;\n this.parent = undefined;\n }\n async _read(batch) {\n if (this.reading)\n return;\n this.reading = true;\n try {\n while (!this.destroyed && batch > 0) {\n const par = this.parent;\n const fil = par && par.files;\n if (fil && fil.length > 0) {\n const { path, depth } = par;\n const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path));\n const awaited = await Promise.all(slice);\n for (const entry of awaited) {\n if (!entry)\n continue;\n if (this.destroyed)\n return;\n const entryType = await this._getEntryType(entry);\n if (entryType === 'directory' && this._directoryFilter(entry)) {\n if (depth <= this._maxDepth) {\n this.parents.push(this._exploreDir(entry.fullPath, depth + 1));\n }\n if (this._wantsDir) {\n this.push(entry);\n batch--;\n }\n }\n else if ((entryType === 'file' || this._includeAsFile(entry)) &&\n this._fileFilter(entry)) {\n if (this._wantsFile) {\n this.push(entry);\n batch--;\n }\n }\n }\n }\n else {\n const parent = this.parents.pop();\n if (!parent) {\n this.push(null);\n break;\n }\n this.parent = await parent;\n if (this.destroyed)\n return;\n }\n }\n }\n catch (error) {\n this.destroy(error);\n }\n finally {\n this.reading = false;\n }\n }\n async _exploreDir(path, depth) {\n let files;\n try {\n files = await readdir(path, this._rdOptions);\n }\n catch (error) {\n this._onError(error);\n }\n return { files, depth, path };\n }\n async _formatEntry(dirent, path) {\n let entry;\n const basename = this._isDirent ? dirent.name : dirent;\n try {\n const fullPath = presolve(pjoin(path, basename));\n entry = { path: prelative(this._root, fullPath), fullPath, basename };\n entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);\n }\n catch (err) {\n this._onError(err);\n return;\n }\n return entry;\n }\n _onError(err) {\n if (isNormalFlowError(err) && !this.destroyed) {\n this.emit('warn', err);\n }\n else {\n this.destroy(err);\n }\n }\n async _getEntryType(entry) {\n // entry may be undefined, because a warning or an error were emitted\n // and the statsProp is undefined\n if (!entry && this._statsProp in entry) {\n return '';\n }\n const stats = entry[this._statsProp];\n if (stats.isFile())\n return 'file';\n if (stats.isDirectory())\n return 'directory';\n if (stats && stats.isSymbolicLink()) {\n const full = entry.fullPath;\n try {\n const entryRealPath = await realpath(full);\n const entryRealPathStats = await lstat(entryRealPath);\n if (entryRealPathStats.isFile()) {\n return 'file';\n }\n if (entryRealPathStats.isDirectory()) {\n const len = entryRealPath.length;\n if (full.startsWith(entryRealPath) && full.substr(len, 1) === psep) {\n const recursiveError = new Error(`Circular symlink detected: \"${full}\" points to \"${entryRealPath}\"`);\n // @ts-ignore\n recursiveError.code = RECURSIVE_ERROR_CODE;\n return this._onError(recursiveError);\n }\n return 'directory';\n }\n }\n catch (error) {\n this._onError(error);\n return '';\n }\n }\n }\n _includeAsFile(entry) {\n const stats = entry && entry[this._statsProp];\n return stats && this._wantsEverything && !stats.isDirectory();\n }\n}\n/**\n * Streaming version: Reads all files and directories in given root recursively.\n * Consumes ~constant small amount of RAM.\n * @param root Root directory\n * @param options Options to specify root (start directory), filters and recursion depth\n */\nexport function readdirp(root, options = {}) {\n // @ts-ignore\n let type = options.entryType || options.type;\n if (type === 'both')\n type = EntryTypes.FILE_DIR_TYPE; // backwards-compatibility\n if (type)\n options.type = type;\n if (!root) {\n throw new Error('readdirp: root argument is required. Usage: readdirp(root, options)');\n }\n else if (typeof root !== 'string') {\n throw new TypeError('readdirp: root argument must be a string. Usage: readdirp(root, options)');\n }\n else if (type && !ALL_TYPES.includes(type)) {\n throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(', ')}`);\n }\n options.root = root;\n return new ReaddirpStream(options);\n}\n/**\n * Promise version: Reads all files and directories in given root recursively.\n * Compared to streaming version, will consume a lot of RAM e.g. when 1 million files are listed.\n * @returns array of paths and their entry infos\n */\nexport function readdirpPromise(root, options = {}) {\n return new Promise((resolve, reject) => {\n const files = [];\n readdirp(root, options)\n .on('data', (entry) => files.push(entry))\n .on('end', () => resolve(files))\n .on('error', (error) => reject(error));\n });\n}\nexport default readdirp;\n",
1222
1222
  "import { watch as fs_watch, unwatchFile, watchFile } from 'node:fs';\nimport { realpath as fsrealpath, lstat, open, stat } from 'node:fs/promises';\nimport { type as osType } from 'node:os';\nimport * as sp from 'node:path';\nexport const STR_DATA = 'data';\nexport const STR_END = 'end';\nexport const STR_CLOSE = 'close';\nexport const EMPTY_FN = () => { };\nexport const IDENTITY_FN = (val) => val;\nconst pl = process.platform;\nexport const isWindows = pl === 'win32';\nexport const isMacos = pl === 'darwin';\nexport const isLinux = pl === 'linux';\nexport const isFreeBSD = pl === 'freebsd';\nexport const isIBMi = osType() === 'OS400';\nexport const EVENTS = {\n ALL: 'all',\n READY: 'ready',\n ADD: 'add',\n CHANGE: 'change',\n ADD_DIR: 'addDir',\n UNLINK: 'unlink',\n UNLINK_DIR: 'unlinkDir',\n RAW: 'raw',\n ERROR: 'error',\n};\nconst EV = EVENTS;\nconst THROTTLE_MODE_WATCH = 'watch';\nconst statMethods = { lstat, stat };\nconst KEY_LISTENERS = 'listeners';\nconst KEY_ERR = 'errHandlers';\nconst KEY_RAW = 'rawEmitters';\nconst HANDLER_KEYS = [KEY_LISTENERS, KEY_ERR, KEY_RAW];\n// prettier-ignore\nconst binaryExtensions = new Set([\n '3dm', '3ds', '3g2', '3gp', '7z', 'a', 'aac', 'adp', 'afdesign', 'afphoto', 'afpub', 'ai',\n 'aif', 'aiff', 'alz', 'ape', 'apk', 'appimage', 'ar', 'arj', 'asf', 'au', 'avi',\n 'bak', 'baml', 'bh', 'bin', 'bk', 'bmp', 'btif', 'bz2', 'bzip2',\n 'cab', 'caf', 'cgm', 'class', 'cmx', 'cpio', 'cr2', 'cur', 'dat', 'dcm', 'deb', 'dex', 'djvu',\n 'dll', 'dmg', 'dng', 'doc', 'docm', 'docx', 'dot', 'dotm', 'dra', 'DS_Store', 'dsk', 'dts',\n 'dtshd', 'dvb', 'dwg', 'dxf',\n 'ecelp4800', 'ecelp7470', 'ecelp9600', 'egg', 'eol', 'eot', 'epub', 'exe',\n 'f4v', 'fbs', 'fh', 'fla', 'flac', 'flatpak', 'fli', 'flv', 'fpx', 'fst', 'fvt',\n 'g3', 'gh', 'gif', 'graffle', 'gz', 'gzip',\n 'h261', 'h263', 'h264', 'icns', 'ico', 'ief', 'img', 'ipa', 'iso',\n 'jar', 'jpeg', 'jpg', 'jpgv', 'jpm', 'jxr', 'key', 'ktx',\n 'lha', 'lib', 'lvp', 'lz', 'lzh', 'lzma', 'lzo',\n 'm3u', 'm4a', 'm4v', 'mar', 'mdi', 'mht', 'mid', 'midi', 'mj2', 'mka', 'mkv', 'mmr', 'mng',\n 'mobi', 'mov', 'movie', 'mp3',\n 'mp4', 'mp4a', 'mpeg', 'mpg', 'mpga', 'mxu',\n 'nef', 'npx', 'numbers', 'nupkg',\n 'o', 'odp', 'ods', 'odt', 'oga', 'ogg', 'ogv', 'otf', 'ott',\n 'pages', 'pbm', 'pcx', 'pdb', 'pdf', 'pea', 'pgm', 'pic', 'png', 'pnm', 'pot', 'potm',\n 'potx', 'ppa', 'ppam',\n 'ppm', 'pps', 'ppsm', 'ppsx', 'ppt', 'pptm', 'pptx', 'psd', 'pya', 'pyc', 'pyo', 'pyv',\n 'qt',\n 'rar', 'ras', 'raw', 'resources', 'rgb', 'rip', 'rlc', 'rmf', 'rmvb', 'rpm', 'rtf', 'rz',\n 's3m', 's7z', 'scpt', 'sgi', 'shar', 'snap', 'sil', 'sketch', 'slk', 'smv', 'snk', 'so',\n 'stl', 'suo', 'sub', 'swf',\n 'tar', 'tbz', 'tbz2', 'tga', 'tgz', 'thmx', 'tif', 'tiff', 'tlz', 'ttc', 'ttf', 'txz',\n 'udf', 'uvh', 'uvi', 'uvm', 'uvp', 'uvs', 'uvu',\n 'viv', 'vob',\n 'war', 'wav', 'wax', 'wbmp', 'wdp', 'weba', 'webm', 'webp', 'whl', 'wim', 'wm', 'wma',\n 'wmv', 'wmx', 'woff', 'woff2', 'wrm', 'wvx',\n 'xbm', 'xif', 'xla', 'xlam', 'xls', 'xlsb', 'xlsm', 'xlsx', 'xlt', 'xltm', 'xltx', 'xm',\n 'xmind', 'xpi', 'xpm', 'xwd', 'xz',\n 'z', 'zip', 'zipx',\n]);\nconst isBinaryPath = (filePath) => binaryExtensions.has(sp.extname(filePath).slice(1).toLowerCase());\n// TODO: emit errors properly. Example: EMFILE on Macos.\nconst foreach = (val, fn) => {\n if (val instanceof Set) {\n val.forEach(fn);\n }\n else {\n fn(val);\n }\n};\nconst addAndConvert = (main, prop, item) => {\n let container = main[prop];\n if (!(container instanceof Set)) {\n main[prop] = container = new Set([container]);\n }\n container.add(item);\n};\nconst clearItem = (cont) => (key) => {\n const set = cont[key];\n if (set instanceof Set) {\n set.clear();\n }\n else {\n delete cont[key];\n }\n};\nconst delFromSet = (main, prop, item) => {\n const container = main[prop];\n if (container instanceof Set) {\n container.delete(item);\n }\n else if (container === item) {\n delete main[prop];\n }\n};\nconst isEmptySet = (val) => (val instanceof Set ? val.size === 0 : !val);\nconst FsWatchInstances = new Map();\n/**\n * Instantiates the fs_watch interface\n * @param path to be watched\n * @param options to be passed to fs_watch\n * @param listener main event handler\n * @param errHandler emits info about errors\n * @param emitRaw emits raw event data\n * @returns {NativeFsWatcher}\n */\nfunction createFsWatchInstance(path, options, listener, errHandler, emitRaw) {\n const handleEvent = (rawEvent, evPath) => {\n listener(path);\n emitRaw(rawEvent, evPath, { watchedPath: path });\n // emit based on events occurring for files from a directory's watcher in\n // case the file's watcher misses it (and rely on throttling to de-dupe)\n if (evPath && path !== evPath) {\n fsWatchBroadcast(sp.resolve(path, evPath), KEY_LISTENERS, sp.join(path, evPath));\n }\n };\n try {\n return fs_watch(path, {\n persistent: options.persistent,\n }, handleEvent);\n }\n catch (error) {\n errHandler(error);\n return undefined;\n }\n}\n/**\n * Helper for passing fs_watch event data to a collection of listeners\n * @param fullPath absolute path bound to fs_watch instance\n */\nconst fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {\n const cont = FsWatchInstances.get(fullPath);\n if (!cont)\n return;\n foreach(cont[listenerType], (listener) => {\n listener(val1, val2, val3);\n });\n};\n/**\n * Instantiates the fs_watch interface or binds listeners\n * to an existing one covering the same file system entry\n * @param path\n * @param fullPath absolute path\n * @param options to be passed to fs_watch\n * @param handlers container for event listener functions\n */\nconst setFsWatchListener = (path, fullPath, options, handlers) => {\n const { listener, errHandler, rawEmitter } = handlers;\n let cont = FsWatchInstances.get(fullPath);\n let watcher;\n if (!options.persistent) {\n watcher = createFsWatchInstance(path, options, listener, errHandler, rawEmitter);\n if (!watcher)\n return;\n return watcher.close.bind(watcher);\n }\n if (cont) {\n addAndConvert(cont, KEY_LISTENERS, listener);\n addAndConvert(cont, KEY_ERR, errHandler);\n addAndConvert(cont, KEY_RAW, rawEmitter);\n }\n else {\n watcher = createFsWatchInstance(path, options, fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS), errHandler, // no need to use broadcast here\n fsWatchBroadcast.bind(null, fullPath, KEY_RAW));\n if (!watcher)\n return;\n watcher.on(EV.ERROR, async (error) => {\n const broadcastErr = fsWatchBroadcast.bind(null, fullPath, KEY_ERR);\n if (cont)\n cont.watcherUnusable = true; // documented since Node 10.4.1\n // Workaround for https://github.com/joyent/node/issues/4337\n if (isWindows && error.code === 'EPERM') {\n try {\n const fd = await open(path, 'r');\n await fd.close();\n broadcastErr(error);\n }\n catch (err) {\n // do nothing\n }\n }\n else {\n broadcastErr(error);\n }\n });\n cont = {\n listeners: listener,\n errHandlers: errHandler,\n rawEmitters: rawEmitter,\n watcher,\n };\n FsWatchInstances.set(fullPath, cont);\n }\n // const index = cont.listeners.indexOf(listener);\n // removes this instance's listeners and closes the underlying fs_watch\n // instance if there are no more listeners left\n return () => {\n delFromSet(cont, KEY_LISTENERS, listener);\n delFromSet(cont, KEY_ERR, errHandler);\n delFromSet(cont, KEY_RAW, rawEmitter);\n if (isEmptySet(cont.listeners)) {\n // Check to protect against issue gh-730.\n // if (cont.watcherUnusable) {\n cont.watcher.close();\n // }\n FsWatchInstances.delete(fullPath);\n HANDLER_KEYS.forEach(clearItem(cont));\n // @ts-ignore\n cont.watcher = undefined;\n Object.freeze(cont);\n }\n };\n};\n// fs_watchFile helpers\n// object to hold per-process fs_watchFile instances\n// (may be shared across chokidar FSWatcher instances)\nconst FsWatchFileInstances = new Map();\n/**\n * Instantiates the fs_watchFile interface or binds listeners\n * to an existing one covering the same file system entry\n * @param path to be watched\n * @param fullPath absolute path\n * @param options options to be passed to fs_watchFile\n * @param handlers container for event listener functions\n * @returns closer\n */\nconst setFsWatchFileListener = (path, fullPath, options, handlers) => {\n const { listener, rawEmitter } = handlers;\n let cont = FsWatchFileInstances.get(fullPath);\n // let listeners = new Set();\n // let rawEmitters = new Set();\n const copts = cont && cont.options;\n if (copts && (copts.persistent < options.persistent || copts.interval > options.interval)) {\n // \"Upgrade\" the watcher to persistence or a quicker interval.\n // This creates some unlikely edge case issues if the user mixes\n // settings in a very weird way, but solving for those cases\n // doesn't seem worthwhile for the added complexity.\n // listeners = cont.listeners;\n // rawEmitters = cont.rawEmitters;\n unwatchFile(fullPath);\n cont = undefined;\n }\n if (cont) {\n addAndConvert(cont, KEY_LISTENERS, listener);\n addAndConvert(cont, KEY_RAW, rawEmitter);\n }\n else {\n // TODO\n // listeners.add(listener);\n // rawEmitters.add(rawEmitter);\n cont = {\n listeners: listener,\n rawEmitters: rawEmitter,\n options,\n watcher: watchFile(fullPath, options, (curr, prev) => {\n foreach(cont.rawEmitters, (rawEmitter) => {\n rawEmitter(EV.CHANGE, fullPath, { curr, prev });\n });\n const currmtime = curr.mtimeMs;\n if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {\n foreach(cont.listeners, (listener) => listener(path, curr));\n }\n }),\n };\n FsWatchFileInstances.set(fullPath, cont);\n }\n // const index = cont.listeners.indexOf(listener);\n // Removes this instance's listeners and closes the underlying fs_watchFile\n // instance if there are no more listeners left.\n return () => {\n delFromSet(cont, KEY_LISTENERS, listener);\n delFromSet(cont, KEY_RAW, rawEmitter);\n if (isEmptySet(cont.listeners)) {\n FsWatchFileInstances.delete(fullPath);\n unwatchFile(fullPath);\n cont.options = cont.watcher = undefined;\n Object.freeze(cont);\n }\n };\n};\n/**\n * @mixin\n */\nexport class NodeFsHandler {\n fsw;\n _boundHandleError;\n constructor(fsW) {\n this.fsw = fsW;\n this._boundHandleError = (error) => fsW._handleError(error);\n }\n /**\n * Watch file for changes with fs_watchFile or fs_watch.\n * @param path to file or dir\n * @param listener on fs change\n * @returns closer for the watcher instance\n */\n _watchWithNodeFs(path, listener) {\n const opts = this.fsw.options;\n const directory = sp.dirname(path);\n const basename = sp.basename(path);\n const parent = this.fsw._getWatchedDir(directory);\n parent.add(basename);\n const absolutePath = sp.resolve(path);\n const options = {\n persistent: opts.persistent,\n };\n if (!listener)\n listener = EMPTY_FN;\n let closer;\n if (opts.usePolling) {\n const enableBin = opts.interval !== opts.binaryInterval;\n options.interval = enableBin && isBinaryPath(basename) ? opts.binaryInterval : opts.interval;\n closer = setFsWatchFileListener(path, absolutePath, options, {\n listener,\n rawEmitter: this.fsw._emitRaw,\n });\n }\n else {\n closer = setFsWatchListener(path, absolutePath, options, {\n listener,\n errHandler: this._boundHandleError,\n rawEmitter: this.fsw._emitRaw,\n });\n }\n return closer;\n }\n /**\n * Watch a file and emit add event if warranted.\n * @returns closer for the watcher instance\n */\n _handleFile(file, stats, initialAdd) {\n if (this.fsw.closed) {\n return;\n }\n const dirname = sp.dirname(file);\n const basename = sp.basename(file);\n const parent = this.fsw._getWatchedDir(dirname);\n // stats is always present\n let prevStats = stats;\n // if the file is already being watched, do nothing\n if (parent.has(basename))\n return;\n const listener = async (path, newStats) => {\n if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))\n return;\n if (!newStats || newStats.mtimeMs === 0) {\n try {\n const newStats = await stat(file);\n if (this.fsw.closed)\n return;\n // Check that change event was not fired because of changed only accessTime.\n const at = newStats.atimeMs;\n const mt = newStats.mtimeMs;\n if (!at || at <= mt || mt !== prevStats.mtimeMs) {\n this.fsw._emit(EV.CHANGE, file, newStats);\n }\n if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats.ino) {\n this.fsw._closeFile(path);\n prevStats = newStats;\n const closer = this._watchWithNodeFs(file, listener);\n if (closer)\n this.fsw._addPathCloser(path, closer);\n }\n else {\n prevStats = newStats;\n }\n }\n catch (error) {\n // Fix issues where mtime is null but file is still present\n this.fsw._remove(dirname, basename);\n }\n // add is about to be emitted if file not already tracked in parent\n }\n else if (parent.has(basename)) {\n // Check that change event was not fired because of changed only accessTime.\n const at = newStats.atimeMs;\n const mt = newStats.mtimeMs;\n if (!at || at <= mt || mt !== prevStats.mtimeMs) {\n this.fsw._emit(EV.CHANGE, file, newStats);\n }\n prevStats = newStats;\n }\n };\n // kick off the watcher\n const closer = this._watchWithNodeFs(file, listener);\n // emit an add event if we're supposed to\n if (!(initialAdd && this.fsw.options.ignoreInitial) && this.fsw._isntIgnored(file)) {\n if (!this.fsw._throttle(EV.ADD, file, 0))\n return;\n this.fsw._emit(EV.ADD, file, stats);\n }\n return closer;\n }\n /**\n * Handle symlinks encountered while reading a dir.\n * @param entry returned by readdirp\n * @param directory path of dir being read\n * @param path of this item\n * @param item basename of this item\n * @returns true if no more processing is needed for this entry.\n */\n async _handleSymlink(entry, directory, path, item) {\n if (this.fsw.closed) {\n return;\n }\n const full = entry.fullPath;\n const dir = this.fsw._getWatchedDir(directory);\n if (!this.fsw.options.followSymlinks) {\n // watch symlink directly (don't follow) and detect changes\n this.fsw._incrReadyCount();\n let linkPath;\n try {\n linkPath = await fsrealpath(path);\n }\n catch (e) {\n this.fsw._emitReady();\n return true;\n }\n if (this.fsw.closed)\n return;\n if (dir.has(item)) {\n if (this.fsw._symlinkPaths.get(full) !== linkPath) {\n this.fsw._symlinkPaths.set(full, linkPath);\n this.fsw._emit(EV.CHANGE, path, entry.stats);\n }\n }\n else {\n dir.add(item);\n this.fsw._symlinkPaths.set(full, linkPath);\n this.fsw._emit(EV.ADD, path, entry.stats);\n }\n this.fsw._emitReady();\n return true;\n }\n // don't follow the same symlink more than once\n if (this.fsw._symlinkPaths.has(full)) {\n return true;\n }\n this.fsw._symlinkPaths.set(full, true);\n }\n _handleRead(directory, initialAdd, wh, target, dir, depth, throttler) {\n // Normalize the directory name on Windows\n directory = sp.join(directory, '');\n const throttleKey = target ? `${directory}:${target}` : directory;\n throttler = this.fsw._throttle('readdir', throttleKey, 1000);\n if (!throttler)\n return;\n const previous = this.fsw._getWatchedDir(wh.path);\n const current = new Set();\n let stream = this.fsw._readdirp(directory, {\n fileFilter: (entry) => wh.filterPath(entry),\n directoryFilter: (entry) => wh.filterDir(entry),\n });\n if (!stream)\n return;\n stream\n .on(STR_DATA, async (entry) => {\n if (this.fsw.closed) {\n stream = undefined;\n return;\n }\n const item = entry.path;\n let path = sp.join(directory, item);\n current.add(item);\n if (entry.stats.isSymbolicLink() &&\n (await this._handleSymlink(entry, directory, path, item))) {\n return;\n }\n if (this.fsw.closed) {\n stream = undefined;\n return;\n }\n // Files that present in current directory snapshot\n // but absent in previous are added to watch list and\n // emit `add` event.\n if (item === target || (!target && !previous.has(item))) {\n this.fsw._incrReadyCount();\n // ensure relativeness of path is preserved in case of watcher reuse\n path = sp.join(dir, sp.relative(dir, path));\n this._addToNodeFs(path, initialAdd, wh, depth + 1);\n }\n })\n .on(EV.ERROR, this._boundHandleError);\n return new Promise((resolve, reject) => {\n if (!stream)\n return reject();\n stream.once(STR_END, () => {\n if (this.fsw.closed) {\n stream = undefined;\n return;\n }\n const wasThrottled = throttler ? throttler.clear() : false;\n resolve(undefined);\n // Files that absent in current directory snapshot\n // but present in previous emit `remove` event\n // and are removed from @watched[directory].\n previous\n .getChildren()\n .filter((item) => {\n return item !== directory && !current.has(item);\n })\n .forEach((item) => {\n this.fsw._remove(directory, item);\n });\n stream = undefined;\n // one more time for any missed in case changes came in extremely quickly\n if (wasThrottled)\n this._handleRead(directory, false, wh, target, dir, depth, throttler);\n });\n });\n }\n /**\n * Read directory to add / remove files from `@watched` list and re-read it on change.\n * @param dir fs path\n * @param stats\n * @param initialAdd\n * @param depth relative to user-supplied path\n * @param target child path targeted for watch\n * @param wh Common watch helpers for this path\n * @param realpath\n * @returns closer for the watcher instance.\n */\n async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath) {\n const parentDir = this.fsw._getWatchedDir(sp.dirname(dir));\n const tracked = parentDir.has(sp.basename(dir));\n if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) {\n this.fsw._emit(EV.ADD_DIR, dir, stats);\n }\n // ensure dir is tracked (harmless if redundant)\n parentDir.add(sp.basename(dir));\n this.fsw._getWatchedDir(dir);\n let throttler;\n let closer;\n const oDepth = this.fsw.options.depth;\n if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath)) {\n if (!target) {\n await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler);\n if (this.fsw.closed)\n return;\n }\n closer = this._watchWithNodeFs(dir, (dirPath, stats) => {\n // if current directory is removed, do nothing\n if (stats && stats.mtimeMs === 0)\n return;\n this._handleRead(dirPath, false, wh, target, dir, depth, throttler);\n });\n }\n return closer;\n }\n /**\n * Handle added file, directory, or glob pattern.\n * Delegates call to _handleFile / _handleDir after checks.\n * @param path to file or ir\n * @param initialAdd was the file added at watch instantiation?\n * @param priorWh depth relative to user-supplied path\n * @param depth Child path actually targeted for watch\n * @param target Child path actually targeted for watch\n */\n async _addToNodeFs(path, initialAdd, priorWh, depth, target) {\n const ready = this.fsw._emitReady;\n if (this.fsw._isIgnored(path) || this.fsw.closed) {\n ready();\n return false;\n }\n const wh = this.fsw._getWatchHelpers(path);\n if (priorWh) {\n wh.filterPath = (entry) => priorWh.filterPath(entry);\n wh.filterDir = (entry) => priorWh.filterDir(entry);\n }\n // evaluate what is at the path we're being asked to watch\n try {\n const stats = await statMethods[wh.statMethod](wh.watchPath);\n if (this.fsw.closed)\n return;\n if (this.fsw._isIgnored(wh.watchPath, stats)) {\n ready();\n return false;\n }\n const follow = this.fsw.options.followSymlinks;\n let closer;\n if (stats.isDirectory()) {\n const absPath = sp.resolve(path);\n const targetPath = follow ? await fsrealpath(path) : path;\n if (this.fsw.closed)\n return;\n closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);\n if (this.fsw.closed)\n return;\n // preserve this symlink's target path\n if (absPath !== targetPath && targetPath !== undefined) {\n this.fsw._symlinkPaths.set(absPath, targetPath);\n }\n }\n else if (stats.isSymbolicLink()) {\n const targetPath = follow ? await fsrealpath(path) : path;\n if (this.fsw.closed)\n return;\n const parent = sp.dirname(wh.watchPath);\n this.fsw._getWatchedDir(parent).add(wh.watchPath);\n this.fsw._emit(EV.ADD, wh.watchPath, stats);\n closer = await this._handleDir(parent, stats, initialAdd, depth, path, wh, targetPath);\n if (this.fsw.closed)\n return;\n // preserve this symlink's target path\n if (targetPath !== undefined) {\n this.fsw._symlinkPaths.set(sp.resolve(path), targetPath);\n }\n }\n else {\n closer = this._handleFile(wh.watchPath, stats, initialAdd);\n }\n ready();\n if (closer)\n this.fsw._addPathCloser(path, closer);\n return false;\n }\n catch (error) {\n if (this.fsw._handleError(error)) {\n ready();\n return path;\n }\n }\n }\n}\n",
1223
1223
  "import type { Command } from \"commander\";\nimport { createDevLogger } from \"@/cli/dev/createDevLogger.js\";\nimport { createDevServer } from \"@/cli/dev/dev-server/main.js\";\nimport type { ServeRunner } from \"@/cli/dev/dev-server/serve-runner.js\";\nimport {\n createServeCommandRunner,\n type ServeCommandRunnerOptions,\n} from \"@/cli/dev/serve-command-runner.js\";\nimport { stopRunnerOnProcessSignals } from \"@/cli/dev/stop-runner-on-signals.js\";\nimport type { CLIContext, RunCommandResult } from \"@/cli/types.js\";\nimport { type AppIdOptions, Base44Command, theme } from \"@/cli/utils/index.js\";\nimport { getDenoWrapperPath } from \"@/core/assets.js\";\nimport { BASE44_APP_ID_ENV_VAR } from \"@/core/consts.js\";\nimport { ConfigInvalidError } from \"@/core/errors.js\";\nimport { getSiteUrl } from \"@/core/project/api.js\";\nimport { readProjectConfig } from \"@/core/project/config.js\";\n\ninterface DevOptions {\n port?: string;\n remote?: boolean;\n}\n\ninterface LinkedApp {\n id: string;\n projectRoot: string;\n}\n\ntype ConfiguredSite = Pick<\n ServeCommandRunnerOptions,\n \"serveCommand\" | \"projectRoot\"\n>;\n\nfunction localServerUrl(port: number): string {\n return `http://localhost:${port}`;\n}\n\nfunction validateDevOptions(command: Command): void {\n const { appId } = command.optsWithGlobals<AppIdOptions>();\n if (appId !== undefined) {\n command.error(\n `base44 dev cannot be used with --app-id or ${BASE44_APP_ID_ENV_VAR}.`,\n );\n }\n const { port, remote } = command.opts<DevOptions>();\n if (remote && port !== undefined) {\n command.error(\n \"--port applies to the local backend, which --remote does not start.\",\n );\n }\n}\n\nfunction requireLinkedProject({ app }: CLIContext): LinkedApp {\n if (!app?.projectRoot) {\n throw new ConfigInvalidError(\n \"base44 dev requires a linked local project. Run it from a project with base44/.app.jsonc.\",\n );\n }\n return { id: app.id, projectRoot: app.projectRoot };\n}\n\nasync function resolveConfiguredSite(\n app: LinkedApp,\n): Promise<ConfiguredSite | undefined> {\n const { project } = await readProjectConfig(app.projectRoot);\n const serveCommand = project.site?.serveCommand;\n return serveCommand ? { serveCommand, projectRoot: project.root } : undefined;\n}\n\nfunction startServeCommand(\n runner: ServeRunner,\n backend: { url: string; shutdown: () => Promise<void> },\n): void {\n stopRunnerOnProcessSignals(runner);\n\n // If the frontend dies, tear the whole dev environment down.\n runner.onExit(() => {\n void backend.shutdown().finally(() => process.exit(1));\n });\n\n createDevLogger(\"backend\", theme.styles.info).log(\n `Backend running on ${backend.url}`,\n );\n runner.start();\n}\n\nasync function remoteDevAction(app: LinkedApp): Promise<RunCommandResult> {\n const site = await resolveConfiguredSite(app);\n if (!site) {\n throw new ConfigInvalidError(\n \"base44 dev --remote serves the frontend against the production backend, but this project has no site.serveCommand in base44/config.jsonc.\",\n );\n }\n\n const appBaseUrl = await getSiteUrl();\n const runner = createServeCommandRunner({\n ...site,\n appId: app.id,\n appBaseUrl,\n });\n stopRunnerOnProcessSignals(runner);\n runner.onExit((code) => process.exit(code ?? 1));\n runner.start();\n\n return {\n outroMessage: `Frontend dev server targets ${theme.styles.bold(appBaseUrl)} — every write hits your live app`,\n };\n}\n\nasync function localDevAction(\n ctx: CLIContext,\n app: LinkedApp,\n options: DevOptions,\n): Promise<RunCommandResult> {\n const site = await resolveConfiguredSite(app);\n const siteUrlPromise = getSiteUrl().catch(() => undefined);\n\n const backend = await createDevServer({\n log: ctx.log,\n port: options.port ? Number(options.port) : undefined,\n denoWrapperPath: getDenoWrapperPath(),\n loadResources: async () => {\n const { functions, entities, project } = await readProjectConfig();\n const siteUrl = await siteUrlPromise;\n return { functions, entities, project, siteUrl };\n },\n });\n const backendUrl = localServerUrl(backend.port);\n\n if (site) {\n const runner = createServeCommandRunner({\n ...site,\n appId: app.id,\n appBaseUrl: backendUrl,\n });\n startServeCommand(runner, { url: backendUrl, shutdown: backend.shutdown });\n }\n\n const outroMessage = site\n ? \"Open your app using the frontend dev server URL\"\n : `Dev server is available at ${theme.colors.links(backendUrl)}`;\n\n return { outroMessage };\n}\n\nasync function devAction(\n ctx: CLIContext,\n options: DevOptions,\n): Promise<RunCommandResult> {\n const app = requireLinkedProject(ctx);\n return options.remote\n ? remoteDevAction(app)\n : localDevAction(ctx, app, options);\n}\n\nexport function getDevCommand(): Command {\n return new Base44Command(\"dev\")\n .description(\"Start the development server\")\n .option(\"-p, --port <number>\", \"Port for the development server\")\n .option(\n \"--remote\",\n \"Serve the frontend against the production backend instead of a local one\",\n )\n .hook(\"preAction\", validateDevOptions)\n .action(devAction);\n}\n",
1224
- "import { z } from \"zod\";\nimport { InvalidInputError } from \"@/core/errors.js\";\n\nconst ACCESS_TOKEN_VAR = \"BASE44_EXEC_ACCESS_TOKEN\";\nconst SERVER_URL_VAR = \"BASE44_EXEC_SERVER_URL\";\nconst SERVICE_TOKEN_VAR = \"BASE44_EXEC_SERVICE_TOKEN\";\nconst HEADERS_VAR = \"BASE44_EXEC_HEADERS\";\n\nexport const EXEC_ENV_VARS = [\n ACCESS_TOKEN_VAR,\n SERVER_URL_VAR,\n SERVICE_TOKEN_VAR,\n HEADERS_VAR,\n] as const;\n\n/**\n * An `exec` target supplied by the environment (e.g. a platform sandbox): the\n * SDK runs against `serverUrl` with `token`, with no platform login or lookup.\n */\ninterface ExecEnvTarget {\n serverUrl: string;\n token: string;\n serviceToken?: string;\n headers?: Record<string, string>;\n}\n\ntype Env = Record<string, string | undefined>;\n\nfunction read(env: Env, name: string): string | undefined {\n return env[name]?.trim() || undefined;\n}\n\n/** Whether any `BASE44_EXEC_*` variable is set, so `exec` skips the login. */\nexport function hasExecEnvTarget(env: Env = process.env): boolean {\n return EXEC_ENV_VARS.some((name) => read(env, name) !== undefined);\n}\n\nconst HeadersSchema = z.record(z.string(), z.string());\n\nfunction parseHeaders(raw: string): Record<string, string> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new InvalidInputError(`${HEADERS_VAR} is not valid JSON.`, {\n hints: [{ message: `Set ${HEADERS_VAR} to e.g. '{\"X-Header\":\"value\"}'` }],\n });\n }\n const result = HeadersSchema.safeParse(parsed);\n if (!result.success) {\n throw new InvalidInputError(\n `${HEADERS_VAR} must be a JSON object of string header values.`,\n {\n hints: [\n { message: `Set ${HEADERS_VAR} to e.g. '{\"X-Header\":\"value\"}'` },\n ],\n },\n );\n }\n return result.data;\n}\n\n/**\n * Read the env-supplied `exec` target. Returns `undefined` when no\n * `BASE44_EXEC_*` variable is set. Error messages never include the values.\n */\nexport function readExecEnvTarget(\n env: Env = process.env,\n): ExecEnvTarget | undefined {\n if (!hasExecEnvTarget(env)) {\n return undefined;\n }\n\n const token = read(env, ACCESS_TOKEN_VAR);\n const serverUrl = read(env, SERVER_URL_VAR);\n if (!token || !serverUrl) {\n const missing = token ? SERVER_URL_VAR : ACCESS_TOKEN_VAR;\n throw new InvalidInputError(\n `${missing} is not set. ${ACCESS_TOKEN_VAR} and ${SERVER_URL_VAR} must be set together.`,\n {\n hints: [\n {\n message: `Set both ${ACCESS_TOKEN_VAR} and ${SERVER_URL_VAR}, or unset all BASE44_EXEC_* variables to use your login`,\n },\n ],\n },\n );\n }\n if (!URL.canParse(serverUrl)) {\n throw new InvalidInputError(`${SERVER_URL_VAR} is not a valid URL.`);\n }\n\n const serviceToken = read(env, SERVICE_TOKEN_VAR);\n const rawHeaders = read(env, HEADERS_VAR);\n return {\n token,\n serverUrl,\n ...(serviceToken ? { serviceToken } : {}),\n ...(rawHeaders ? { headers: parseHeaders(rawHeaders) } : {}),\n };\n}\n",
1224
+ "import { z } from \"zod\";\nimport { InvalidInputError } from \"@/core/errors.js\";\n\nconst ACCESS_TOKEN_VAR = \"BASE44_EXEC_ACCESS_TOKEN\";\nconst SERVER_URL_VAR = \"BASE44_EXEC_SERVER_URL\";\nconst SERVICE_TOKEN_VAR = \"BASE44_EXEC_SERVICE_TOKEN\";\nconst HEADERS_VAR = \"BASE44_EXEC_HEADERS\";\n\nexport const EXEC_ENV_VARS = [\n ACCESS_TOKEN_VAR,\n SERVER_URL_VAR,\n SERVICE_TOKEN_VAR,\n HEADERS_VAR,\n] as const;\n\n/**\n * An `exec` target supplied by the environment (e.g. a platform sandbox): the\n * SDK runs against `serverUrl` with `token`, with no platform login or lookup.\n */\nexport interface ExecEnvTarget {\n serverUrl: string;\n token: string;\n serviceToken?: string;\n headers?: Record<string, string>;\n}\n\ntype Env = Record<string, string | undefined>;\n\nfunction read(env: Env, name: string): string | undefined {\n return env[name]?.trim() || undefined;\n}\n\n/** Whether any `BASE44_EXEC_*` variable is set, so `exec` skips the login. */\nexport function hasExecEnvTarget(env: Env = process.env): boolean {\n return EXEC_ENV_VARS.some((name) => read(env, name) !== undefined);\n}\n\nconst HeadersSchema = z.record(z.string(), z.string());\n\nfunction parseHeaders(raw: string): Record<string, string> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new InvalidInputError(`${HEADERS_VAR} is not valid JSON.`, {\n hints: [{ message: `Set ${HEADERS_VAR} to e.g. '{\"X-Header\":\"value\"}'` }],\n });\n }\n const result = HeadersSchema.safeParse(parsed);\n if (!result.success) {\n throw new InvalidInputError(\n `${HEADERS_VAR} must be a JSON object of string header values.`,\n {\n hints: [\n { message: `Set ${HEADERS_VAR} to e.g. '{\"X-Header\":\"value\"}'` },\n ],\n },\n );\n }\n return result.data;\n}\n\n/**\n * Read the env-supplied `exec` target. Returns `undefined` when no\n * `BASE44_EXEC_*` variable is set. Error messages never include the values.\n */\nexport function readExecEnvTarget(\n env: Env = process.env,\n): ExecEnvTarget | undefined {\n if (!hasExecEnvTarget(env)) {\n return undefined;\n }\n\n const token = read(env, ACCESS_TOKEN_VAR);\n const serverUrl = read(env, SERVER_URL_VAR);\n if (!token || !serverUrl) {\n const missing = token ? SERVER_URL_VAR : ACCESS_TOKEN_VAR;\n throw new InvalidInputError(\n `${missing} is not set. ${ACCESS_TOKEN_VAR} and ${SERVER_URL_VAR} must be set together.`,\n {\n hints: [\n {\n message: `Set both ${ACCESS_TOKEN_VAR} and ${SERVER_URL_VAR}, or unset all BASE44_EXEC_* variables to use your login`,\n },\n ],\n },\n );\n }\n if (!URL.canParse(serverUrl)) {\n throw new InvalidInputError(`${SERVER_URL_VAR} is not a valid URL.`);\n }\n\n const serviceToken = read(env, SERVICE_TOKEN_VAR);\n const rawHeaders = read(env, HEADERS_VAR);\n return {\n token,\n serverUrl,\n ...(serviceToken ? { serviceToken } : {}),\n ...(rawHeaders ? { headers: parseHeaders(rawHeaders) } : {}),\n };\n}\n",
1225
1225
  "import { spawn } from \"node:child_process\";\nimport { copyFileSync, writeFileSync } from \"node:fs\";\nimport { file } from \"tmp-promise\";\nimport { getExecWrapperPath } from \"@/core/assets.js\";\nimport { EXEC_ENV_VARS } from \"@/core/exec/env-target.js\";\nimport { getAppUserToken, getSiteUrl } from \"@/core/project/api.js\";\nimport { verifyDenoInstalled } from \"@/core/utils/index.js\";\n\ninterface RunScriptOptions {\n appId: string;\n code: string;\n /**\n * When set (a local `base44 dev` server, or an env-supplied target), the\n * SDK's `serverUrl` and access token are taken from here rather than fetched\n * via `getSiteUrl()` / `getAppUserToken()`.\n */\n local?: { serverUrl: string; token: string };\n privileged?: boolean;\n dataEnv?: string;\n /** Passed to `createClient` as `serviceToken`, enabling `base44.asServiceRole`. */\n serviceToken?: string;\n /** Extra headers on every SDK request; `--privileged` / `--data-env` win. */\n headers?: Record<string, string>;\n}\n\n// The wrapper reads and deletes these before the user script runs.\nconst SERVICE_TOKEN_ENV = \"BASE44_SERVICE_TOKEN\";\nconst EXTRA_HEADERS_ENV = \"BASE44_EXTRA_HEADERS\";\n\nfunction wrapperOnlyEnv(\n serviceToken: string | undefined,\n headers: Record<string, string> | undefined,\n): Record<string, string> {\n return {\n ...(serviceToken ? { [SERVICE_TOKEN_ENV]: serviceToken } : {}),\n ...(headers ? { [EXTRA_HEADERS_ENV]: JSON.stringify(headers) } : {}),\n };\n}\n\nfunction inheritedEnv(): NodeJS.ProcessEnv {\n const env = { ...process.env };\n for (const name of [...EXEC_ENV_VARS, SERVICE_TOKEN_ENV, EXTRA_HEADERS_ENV]) {\n delete env[name];\n }\n return env;\n}\n\ninterface RunScriptResult {\n exitCode: number;\n}\n\nexport async function runScript(\n options: RunScriptOptions,\n): Promise<RunScriptResult> {\n const { appId, code, local, privileged, dataEnv, serviceToken, headers } =\n options;\n\n verifyDenoInstalled(\"to run scripts with exec\");\n\n const cleanupFns: (() => void)[] = [];\n\n const tempScript = await file({ postfix: \".ts\" });\n cleanupFns.push(tempScript.cleanup);\n writeFileSync(tempScript.path, code, \"utf-8\");\n const scriptPath = `file://${tempScript.path}`;\n\n // Local mode uses the caller-provided token + local server URL; remote mode\n // fetches the app-user token and the published site URL.\n const [appUserToken, appBaseUrl] = local\n ? [local.token, local.serverUrl]\n : await Promise.all([getAppUserToken(), getSiteUrl()]);\n\n // Copy the exec wrapper to a temp location outside node_modules.\n // This works with both Deno 1.x and 2.x, but is required for Deno 2.x\n // which treats files inside node_modules as Node modules and blocks\n // npm: specifiers in them.\n const tempWrapper = await file({ postfix: \".ts\" });\n cleanupFns.push(tempWrapper.cleanup);\n copyFileSync(getExecWrapperPath(), tempWrapper.path);\n\n try {\n const exitCode = await new Promise<number>((resolvePromise) => {\n const child = spawn(\n \"deno\",\n [\"run\", \"--allow-all\", \"--node-modules-dir=auto\", tempWrapper.path],\n {\n env: {\n ...inheritedEnv(),\n ...wrapperOnlyEnv(serviceToken, headers),\n SCRIPT_PATH: scriptPath,\n BASE44_APP_ID: appId,\n BASE44_ACCESS_TOKEN: appUserToken,\n BASE44_APP_BASE_URL: appBaseUrl,\n ...(privileged ? { BASE44_PRIVILEGED: \"true\" } : {}),\n ...(dataEnv ? { BASE44_DATA_ENV: dataEnv } : {}),\n },\n stdio: \"inherit\",\n },\n );\n\n child.on(\"close\", (code) => {\n resolvePromise(code ?? 1);\n });\n });\n\n return { exitCode };\n } finally {\n for (const cleanup of cleanupFns) {\n cleanup();\n }\n }\n}\n",
1226
1226
  "import type { Command } from \"commander\";\nimport { createJwtToken } from \"@/cli/dev/dev-server/auth/tokens.js\";\nimport type { CLIContext, RunCommandResult } from \"@/cli/types.js\";\nimport { Base44Command } from \"@/cli/utils/index.js\";\nimport { DEFAULT_DEV_SERVER_PORT } from \"@/core/consts.js\";\nimport { InvalidInputError } from \"@/core/errors.js\";\nimport {\n hasExecEnvTarget,\n readExecEnvTarget,\n runScript,\n} from \"@/core/exec/index.js\";\nimport { readAuth } from \"@/core/index.js\";\n\ninterface ExecOptions {\n local?: boolean;\n port?: string;\n privileged?: boolean;\n dataEnv?: string;\n}\n\nfunction readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n let data = \"\";\n process.stdin.setEncoding(\"utf-8\");\n process.stdin.on(\"data\", (chunk: string) => {\n data += chunk;\n });\n process.stdin.on(\"end\", () => resolve(data));\n process.stdin.on(\"error\", reject);\n });\n}\n\nfunction parsePort(value: string): number {\n const port = Number(value);\n if (!Number.isInteger(port) || port <= 0 || port > 65535) {\n throw new InvalidInputError(`Invalid --port value: \"${value}\".`, {\n hints: [{ message: \"Pass a port number, e.g. --port 4400\" }],\n });\n }\n return port;\n}\n\n/**\n * Resolve the local `base44 dev` target: the SDK talks to\n * `http://localhost:<port>` authenticated as the current user via a\n * locally-minted dev JWT. The dev server decodes (does not verify) the token,\n * and seeds the current user as an admin, so this matches the local session.\n */\nasync function resolveLocalTarget(\n port: number,\n): Promise<{ serverUrl: string; token: string }> {\n const { email } = await readAuth();\n return {\n serverUrl: `http://localhost:${port}`,\n token: createJwtToken(email),\n };\n}\n\nasync function execAction(\n { app, isNonInteractive }: CLIContext,\n options: ExecOptions,\n): Promise<RunCommandResult> {\n if (options.port !== undefined && !options.local) {\n throw new InvalidInputError(\"--port can only be used with --local.\", {\n hints: [{ message: \"Usage: base44 exec --local --port <port>\" }],\n });\n }\n\n const envTarget = readExecEnvTarget();\n if (envTarget && options.local) {\n throw new InvalidInputError(\n \"--local cannot be used when BASE44_EXEC_ACCESS_TOKEN and BASE44_EXEC_SERVER_URL are set.\",\n );\n }\n\n const noInputError = new InvalidInputError(\n \"No input provided. Pipe a script to stdin.\",\n {\n hints: [\n { message: \"File: cat ./script.ts | base44 exec\" },\n {\n message:\n 'Eval: echo \"const users = await base44.entities.User.list(); console.log(users)\" | base44 exec',\n },\n ],\n },\n );\n\n if (!isNonInteractive) {\n throw noInputError;\n }\n\n const code = await readStdin();\n\n if (!code.trim()) {\n throw noInputError;\n }\n\n const local = envTarget\n ? { serverUrl: envTarget.serverUrl, token: envTarget.token }\n : options.local\n ? await resolveLocalTarget(\n options.port !== undefined\n ? parsePort(options.port)\n : DEFAULT_DEV_SERVER_PORT,\n )\n : undefined;\n\n const { exitCode } = await runScript({\n appId: app!.id,\n code,\n local,\n privileged: options.privileged,\n dataEnv: options.dataEnv,\n serviceToken: envTarget?.serviceToken,\n headers: envTarget?.headers,\n });\n\n if (exitCode !== 0) {\n process.exitCode = exitCode;\n }\n\n return {};\n}\n\nexport function getExecCommand(): Command {\n // An env-supplied target (e.g. a platform sandbox) needs no platform login.\n return new Base44Command(\"exec\", { requireAuth: () => !hasExecEnvTarget() })\n .description(\n \"Run a script with the Base44 SDK pre-authenticated as the current user\",\n )\n .option(\n \"--local\",\n \"Run against the local `base44 dev` server instead of the deployed app\",\n )\n .option(\n \"--port <number>\",\n `Port the local dev server is on (with --local; defaults to ${DEFAULT_DEV_SERVER_PORT})`,\n )\n .option(\n \"--privileged\",\n \"Run with admin privileges (bypass RLS). Requires app owner/editor role.\",\n )\n .option(\n \"--data-env <environment>\",\n \"Data environment to run against (e.g. dev, prod)\",\n )\n .addHelpText(\n \"after\",\n `\nExamples:\n Run a script file:\n $ cat ./script.ts | base44 exec\n\n Inline script:\n $ echo \"const users = await base44.entities.User.list()\" | base44 exec\n\n Against the local dev server (base44 dev must be running):\n $ echo \"await base44.entities.Task.create({ title: 'seed' })\" | base44 exec --local\n\n With privileged access (bypass RLS):\n $ echo \"const all = await base44.entities.Task.list()\" | base44 exec --privileged\n\nEnvironment (for sandboxes and agents; no login or linked project needed):\n BASE44_EXEC_ACCESS_TOKEN App-user token for the SDK (requires BASE44_EXEC_SERVER_URL)\n BASE44_EXEC_SERVER_URL SDK server URL (requires BASE44_EXEC_ACCESS_TOKEN)\n BASE44_EXEC_SERVICE_TOKEN Optional service token, enables base44.asServiceRole\n BASE44_EXEC_HEADERS Optional JSON object of extra request headers\n The app id comes from --app-id or BASE44_APP_ID.`,\n )\n .action(execAction);\n}\n",
1227
1227
  "import { resolve } from \"node:path\";\nimport type { Option as PromptOption } from \"@clack/prompts\";\nimport { cancel, confirm, isCancel, select, text } from \"@clack/prompts\";\nimport { type Command, Option as CommanderOption } from \"commander\";\nimport { execa } from \"execa\";\nimport kebabCase from \"lodash/kebabCase\";\nimport { deployAction } from \"@/cli/commands/project/deploy.js\";\nimport { CLIExitError } from \"@/cli/errors.js\";\nimport type { CLIContext, RunCommandResult } from \"@/cli/types.js\";\nimport { Base44Command, theme } from \"@/cli/utils/index.js\";\nimport type { Project } from \"@/core/index.js\";\nimport {\n createProject,\n createProjectFilesForExistingProject,\n InvalidInputError,\n isDirEmpty,\n listProjects,\n readProjectConfig,\n setAppContext,\n writeAppConfig,\n writeFile,\n} from \"@/core/index.js\";\nimport { readExplicitAppId } from \"./app-id-options.js\";\n\ninterface EjectOptions {\n path?: string;\n yes?: boolean;\n}\n\nasync function eject(\n ctx: CLIContext,\n options: EjectOptions,\n command: Command,\n): Promise<RunCommandResult> {\n const { log, runTask, isNonInteractive } = ctx;\n const {\n appId,\n legacyProjectId,\n value: selectedAppId,\n } = readExplicitAppId(command);\n if (appId && legacyProjectId) {\n throw new InvalidInputError(\n \"--app-id and --project-id cannot be used together\",\n );\n }\n\n if (isNonInteractive && !selectedAppId) {\n throw new InvalidInputError(\"--app-id is required in non-interactive mode\");\n }\n if (isNonInteractive && !options.path) {\n throw new InvalidInputError(\"--path is required in non-interactive mode\");\n }\n\n const projects = await listProjects();\n const ejectableProjects = projects.filter(\n (p) => p.isManagedSourceCode !== false,\n );\n\n let selectedProject: Project;\n\n if (selectedAppId) {\n const foundProject = ejectableProjects.find((p) => p.id === selectedAppId);\n\n if (!foundProject) {\n throw new InvalidInputError(\n `App with ID \"${selectedAppId}\" not found or not ejectable`,\n {\n hints: [\n {\n message:\n \"Run 'base44 eject' without --app-id to see available projects\",\n },\n ],\n },\n );\n }\n\n selectedProject = foundProject;\n log.info(`Selected project: ${theme.styles.bold(selectedProject.name)}`);\n } else {\n if (ejectableProjects.length === 0) {\n return { outroMessage: \"No projects available to eject.\" };\n }\n\n const projectOptions: PromptOption<Project>[] = ejectableProjects.map(\n (p) => ({\n value: p,\n label: p.name,\n hint: p.userDescription ?? undefined,\n }),\n );\n\n const selected = await select({\n message: `Choose a project to download ${theme.styles.dim(\"(Note: this will clone the selected project)\")}`,\n options: projectOptions,\n });\n\n if (isCancel(selected)) {\n cancel(\"Operation cancelled.\");\n throw new CLIExitError(0);\n }\n selectedProject = selected;\n }\n\n const projectId = selectedProject.id;\n const suggestedPath = (await isDirEmpty())\n ? `./`\n : `./${kebabCase(selectedProject.name)}`;\n\n const selectedPath =\n options.path ??\n (await text({\n message: \"Where should we create your project?\",\n placeholder: suggestedPath,\n initialValue: suggestedPath,\n }));\n\n if (isCancel(selectedPath)) {\n cancel(\"Operation cancelled.\");\n throw new CLIExitError(0);\n }\n\n const resolvedPath = resolve(selectedPath);\n\n await runTask(\n \"Downloading your project's code...\",\n async (updateMessage) => {\n await createProjectFilesForExistingProject({\n projectId,\n projectPath: resolvedPath,\n });\n\n updateMessage(\"Creating a new project...\");\n\n const newProjectName = `${selectedProject.name} Copy`;\n const { projectId: newProjectId } = await createProject(\n newProjectName,\n selectedProject.userDescription ?? undefined,\n );\n\n updateMessage(\"Linking the project...\");\n\n await writeAppConfig(resolvedPath, newProjectId);\n await writeFile(\n `${resolvedPath}/.env.local`,\n `VITE_BASE44_APP_ID=${newProjectId}`,\n );\n\n setAppContext({ id: newProjectId, projectRoot: resolvedPath });\n },\n {\n successMessage: theme.colors.base44Orange(\"Project pulled successfully\"),\n errorMessage: \"Failed to pull project\",\n },\n );\n\n const { project } = await readProjectConfig(resolvedPath);\n const site = project.site;\n\n // Both commands default inside a `site` block, so having one is the whole\n // condition: a backend-only project has nothing to build.\n if (site) {\n const shouldDeploy = options.yes\n ? true\n : await confirm({\n message: \"Would you like to deploy your project now?\",\n });\n\n if (!isCancel(shouldDeploy) && shouldDeploy) {\n await runTask(\n \"Installing dependencies...\",\n async (updateMessage) => {\n await execa({\n cwd: resolvedPath,\n shell: true,\n })`${site.installCommand}`;\n\n updateMessage(\"Building project...\");\n await execa({\n cwd: resolvedPath,\n shell: true,\n })`${site.buildCommand}`;\n },\n {\n successMessage: theme.colors.base44Orange(\n \"Project built successfully\",\n ),\n errorMessage: \"Failed to build project\",\n },\n );\n\n await deployAction(ctx, {\n yes: true,\n build: false,\n projectRoot: resolvedPath,\n });\n }\n }\n\n return { outroMessage: \"Your new project is set and ready to use\" };\n}\n\nexport function getEjectCommand(): Command {\n return (\n new Base44Command(\"eject\", {\n requireAppContext: false,\n })\n .description(\"Download the code for an existing Base44 project\")\n .configureHelp({ showGlobalOptions: true })\n .option(\"-p, --path <path>\", \"Path where to write the project\")\n .option(\"-y, --yes\", \"Skip confirmation prompts\")\n // TODO: Remove --project-id once docs and Base44 CLI skills use --app-id. Kept hidden for backward compatibility.\n .addOption(\n new CommanderOption(\n \"--project-id <id>\",\n \"Project ID to eject (skips interactive selection)\",\n ).hideHelp(),\n )\n .action(eject)\n );\n}\n",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44-preview/cli",
3
- "version": "0.1.22-pr.644.4a848b7",
3
+ "version": "0.1.22-pr.644.52a9e2d",
4
4
  "description": "Base44 CLI - Unified interface for managing Base44 applications",
5
5
  "type": "module",
6
6
  "bin": {