@specific.dev/cli 0.1.46 → 0.1.48
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.
- package/dist/admin/404/index.html +1 -1
- package/dist/admin/404.html +1 -1
- package/dist/admin/__next.__PAGE__.txt +2 -2
- package/dist/admin/__next._full.txt +2 -2
- package/dist/admin/__next._head.txt +1 -1
- package/dist/admin/__next._index.txt +1 -1
- package/dist/admin/__next._tree.txt +1 -1
- package/dist/admin/_next/static/chunks/{e13659c7ad8234ce.js → 75cb455f07e7651a.js} +1 -1
- package/dist/admin/_not-found/__next._full.txt +1 -1
- package/dist/admin/_not-found/__next._head.txt +1 -1
- package/dist/admin/_not-found/__next._index.txt +1 -1
- package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
- package/dist/admin/_not-found/__next._not-found.txt +1 -1
- package/dist/admin/_not-found/__next._tree.txt +1 -1
- package/dist/admin/_not-found/index.html +1 -1
- package/dist/admin/_not-found/index.txt +1 -1
- package/dist/admin/databases/__next._full.txt +1 -1
- package/dist/admin/databases/__next._head.txt +1 -1
- package/dist/admin/databases/__next._index.txt +1 -1
- package/dist/admin/databases/__next._tree.txt +1 -1
- package/dist/admin/databases/__next.databases.__PAGE__.txt +1 -1
- package/dist/admin/databases/__next.databases.txt +1 -1
- package/dist/admin/databases/index.html +1 -1
- package/dist/admin/databases/index.txt +1 -1
- package/dist/admin/index.html +1 -1
- package/dist/admin/index.txt +2 -2
- package/dist/cli.js +630 -403
- package/package.json +7 -2
- /package/dist/admin/_next/static/{uXSe8Dmoqn0jmhvY6Iln0 → dyH4SZNKyN31L1iV-yPZA}/_buildManifest.js +0 -0
- /package/dist/admin/_next/static/{uXSe8Dmoqn0jmhvY6Iln0 → dyH4SZNKyN31L1iV-yPZA}/_clientMiddlewareManifest.json +0 -0
- /package/dist/admin/_next/static/{uXSe8Dmoqn0jmhvY6Iln0 → dyH4SZNKyN31L1iV-yPZA}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -238,15 +238,15 @@ var init_wsl_utils = __esm({
|
|
|
238
238
|
const { stdout } = await executePowerShell(command, { powerShellPath: psPath });
|
|
239
239
|
return stdout.trim();
|
|
240
240
|
};
|
|
241
|
-
convertWslPathToWindows = async (
|
|
242
|
-
if (/^[a-z]+:\/\//i.test(
|
|
243
|
-
return
|
|
241
|
+
convertWslPathToWindows = async (path23) => {
|
|
242
|
+
if (/^[a-z]+:\/\//i.test(path23)) {
|
|
243
|
+
return path23;
|
|
244
244
|
}
|
|
245
245
|
try {
|
|
246
|
-
const { stdout } = await execFile2("wslpath", ["-aw",
|
|
246
|
+
const { stdout } = await execFile2("wslpath", ["-aw", path23], { encoding: "utf8" });
|
|
247
247
|
return stdout.trim();
|
|
248
248
|
} catch {
|
|
249
|
-
return
|
|
249
|
+
return path23;
|
|
250
250
|
}
|
|
251
251
|
};
|
|
252
252
|
}
|
|
@@ -754,8 +754,8 @@ var require_dist = __commonJS({
|
|
|
754
754
|
var $global, $module, $NaN = NaN;
|
|
755
755
|
if ("undefined" != typeof window ? $global = window : "undefined" != typeof self ? $global = self : "undefined" != typeof global ? ($global = global).require = __require : $global = this, void 0 === $global || void 0 === $global.Array) throw new Error("no global object found");
|
|
756
756
|
if ("undefined" != typeof module && ($module = module), !$global.fs && $global.require) try {
|
|
757
|
-
var
|
|
758
|
-
"object" == typeof
|
|
757
|
+
var fs25 = $global.require("fs");
|
|
758
|
+
"object" == typeof fs25 && null !== fs25 && 0 !== Object.keys(fs25).length && ($global.fs = fs25);
|
|
759
759
|
} catch (e) {
|
|
760
760
|
}
|
|
761
761
|
if (!$global.fs) {
|
|
@@ -183873,7 +183873,7 @@ function trackEvent(event, properties) {
|
|
|
183873
183873
|
event,
|
|
183874
183874
|
properties: {
|
|
183875
183875
|
...properties,
|
|
183876
|
-
cli_version: "0.1.
|
|
183876
|
+
cli_version: "0.1.48",
|
|
183877
183877
|
platform: process.platform,
|
|
183878
183878
|
node_version: process.version,
|
|
183879
183879
|
project_id: getProjectId(),
|
|
@@ -184166,11 +184166,11 @@ import { join as join6, dirname as dirname2 } from "path";
|
|
|
184166
184166
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
184167
184167
|
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
184168
184168
|
var docsDir = join6(__dirname2, "docs");
|
|
184169
|
-
function docsCommand(
|
|
184170
|
-
const docPath = resolveDocPath(
|
|
184169
|
+
function docsCommand(path23) {
|
|
184170
|
+
const docPath = resolveDocPath(path23);
|
|
184171
184171
|
if (!docPath) {
|
|
184172
184172
|
console.error(
|
|
184173
|
-
`Documentation not found: ${
|
|
184173
|
+
`Documentation not found: ${path23 || "index"}
|
|
184174
184174
|
|
|
184175
184175
|
Run 'specific docs' to see available topics.`
|
|
184176
184176
|
);
|
|
@@ -184179,16 +184179,16 @@ Run 'specific docs' to see available topics.`
|
|
|
184179
184179
|
const content = readFileSync5(docPath, "utf-8");
|
|
184180
184180
|
console.log(content);
|
|
184181
184181
|
}
|
|
184182
|
-
function resolveDocPath(
|
|
184183
|
-
if (!
|
|
184182
|
+
function resolveDocPath(path23) {
|
|
184183
|
+
if (!path23) {
|
|
184184
184184
|
const indexPath2 = join6(docsDir, "index.md");
|
|
184185
184185
|
return existsSync5(indexPath2) ? indexPath2 : null;
|
|
184186
184186
|
}
|
|
184187
|
-
const directPath = join6(docsDir, `${
|
|
184187
|
+
const directPath = join6(docsDir, `${path23}.md`);
|
|
184188
184188
|
if (existsSync5(directPath)) {
|
|
184189
184189
|
return directPath;
|
|
184190
184190
|
}
|
|
184191
|
-
const indexPath = join6(docsDir,
|
|
184191
|
+
const indexPath = join6(docsDir, path23, "index.md");
|
|
184192
184192
|
if (existsSync5(indexPath)) {
|
|
184193
184193
|
return indexPath;
|
|
184194
184194
|
}
|
|
@@ -184767,8 +184767,8 @@ function checkCommand() {
|
|
|
184767
184767
|
import React6, { useState as useState5, useEffect as useEffect3, useRef } from "react";
|
|
184768
184768
|
import { render as render4, Text as Text6, Box as Box6, useApp as useApp2, Static } from "ink";
|
|
184769
184769
|
import Spinner4 from "ink-spinner";
|
|
184770
|
-
import * as
|
|
184771
|
-
import * as
|
|
184770
|
+
import * as fs20 from "fs";
|
|
184771
|
+
import * as path17 from "path";
|
|
184772
184772
|
|
|
184773
184773
|
// node_modules/.pnpm/chokidar@5.0.0/node_modules/chokidar/index.js
|
|
184774
184774
|
import { EventEmitter } from "node:events";
|
|
@@ -184860,7 +184860,7 @@ var ReaddirpStream = class extends Readable {
|
|
|
184860
184860
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
184861
184861
|
const statMethod = opts.lstat ? lstat : stat;
|
|
184862
184862
|
if (wantBigintFsStats) {
|
|
184863
|
-
this._stat = (
|
|
184863
|
+
this._stat = (path23) => statMethod(path23, { bigint: true });
|
|
184864
184864
|
} else {
|
|
184865
184865
|
this._stat = statMethod;
|
|
184866
184866
|
}
|
|
@@ -184885,8 +184885,8 @@ var ReaddirpStream = class extends Readable {
|
|
|
184885
184885
|
const par = this.parent;
|
|
184886
184886
|
const fil = par && par.files;
|
|
184887
184887
|
if (fil && fil.length > 0) {
|
|
184888
|
-
const { path:
|
|
184889
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
184888
|
+
const { path: path23, depth } = par;
|
|
184889
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path23));
|
|
184890
184890
|
const awaited = await Promise.all(slice);
|
|
184891
184891
|
for (const entry of awaited) {
|
|
184892
184892
|
if (!entry)
|
|
@@ -184926,20 +184926,20 @@ var ReaddirpStream = class extends Readable {
|
|
|
184926
184926
|
this.reading = false;
|
|
184927
184927
|
}
|
|
184928
184928
|
}
|
|
184929
|
-
async _exploreDir(
|
|
184929
|
+
async _exploreDir(path23, depth) {
|
|
184930
184930
|
let files;
|
|
184931
184931
|
try {
|
|
184932
|
-
files = await readdir(
|
|
184932
|
+
files = await readdir(path23, this._rdOptions);
|
|
184933
184933
|
} catch (error) {
|
|
184934
184934
|
this._onError(error);
|
|
184935
184935
|
}
|
|
184936
|
-
return { files, depth, path:
|
|
184936
|
+
return { files, depth, path: path23 };
|
|
184937
184937
|
}
|
|
184938
|
-
async _formatEntry(dirent,
|
|
184938
|
+
async _formatEntry(dirent, path23) {
|
|
184939
184939
|
let entry;
|
|
184940
184940
|
const basename5 = this._isDirent ? dirent.name : dirent;
|
|
184941
184941
|
try {
|
|
184942
|
-
const fullPath = presolve(pjoin(
|
|
184942
|
+
const fullPath = presolve(pjoin(path23, basename5));
|
|
184943
184943
|
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename5 };
|
|
184944
184944
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
184945
184945
|
} catch (err) {
|
|
@@ -185339,16 +185339,16 @@ var delFromSet = (main, prop, item) => {
|
|
|
185339
185339
|
};
|
|
185340
185340
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
185341
185341
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
185342
|
-
function createFsWatchInstance(
|
|
185342
|
+
function createFsWatchInstance(path23, options2, listener, errHandler, emitRaw) {
|
|
185343
185343
|
const handleEvent = (rawEvent, evPath) => {
|
|
185344
|
-
listener(
|
|
185345
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
185346
|
-
if (evPath &&
|
|
185347
|
-
fsWatchBroadcast(sp.resolve(
|
|
185344
|
+
listener(path23);
|
|
185345
|
+
emitRaw(rawEvent, evPath, { watchedPath: path23 });
|
|
185346
|
+
if (evPath && path23 !== evPath) {
|
|
185347
|
+
fsWatchBroadcast(sp.resolve(path23, evPath), KEY_LISTENERS, sp.join(path23, evPath));
|
|
185348
185348
|
}
|
|
185349
185349
|
};
|
|
185350
185350
|
try {
|
|
185351
|
-
return fs_watch(
|
|
185351
|
+
return fs_watch(path23, {
|
|
185352
185352
|
persistent: options2.persistent
|
|
185353
185353
|
}, handleEvent);
|
|
185354
185354
|
} catch (error) {
|
|
@@ -185364,12 +185364,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
185364
185364
|
listener(val1, val2, val3);
|
|
185365
185365
|
});
|
|
185366
185366
|
};
|
|
185367
|
-
var setFsWatchListener = (
|
|
185367
|
+
var setFsWatchListener = (path23, fullPath, options2, handlers) => {
|
|
185368
185368
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
185369
185369
|
let cont = FsWatchInstances.get(fullPath);
|
|
185370
185370
|
let watcher;
|
|
185371
185371
|
if (!options2.persistent) {
|
|
185372
|
-
watcher = createFsWatchInstance(
|
|
185372
|
+
watcher = createFsWatchInstance(path23, options2, listener, errHandler, rawEmitter);
|
|
185373
185373
|
if (!watcher)
|
|
185374
185374
|
return;
|
|
185375
185375
|
return watcher.close.bind(watcher);
|
|
@@ -185380,7 +185380,7 @@ var setFsWatchListener = (path22, fullPath, options2, handlers) => {
|
|
|
185380
185380
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
185381
185381
|
} else {
|
|
185382
185382
|
watcher = createFsWatchInstance(
|
|
185383
|
-
|
|
185383
|
+
path23,
|
|
185384
185384
|
options2,
|
|
185385
185385
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
185386
185386
|
errHandler,
|
|
@@ -185395,7 +185395,7 @@ var setFsWatchListener = (path22, fullPath, options2, handlers) => {
|
|
|
185395
185395
|
cont.watcherUnusable = true;
|
|
185396
185396
|
if (isWindows && error.code === "EPERM") {
|
|
185397
185397
|
try {
|
|
185398
|
-
const fd = await open2(
|
|
185398
|
+
const fd = await open2(path23, "r");
|
|
185399
185399
|
await fd.close();
|
|
185400
185400
|
broadcastErr(error);
|
|
185401
185401
|
} catch (err) {
|
|
@@ -185426,7 +185426,7 @@ var setFsWatchListener = (path22, fullPath, options2, handlers) => {
|
|
|
185426
185426
|
};
|
|
185427
185427
|
};
|
|
185428
185428
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
185429
|
-
var setFsWatchFileListener = (
|
|
185429
|
+
var setFsWatchFileListener = (path23, fullPath, options2, handlers) => {
|
|
185430
185430
|
const { listener, rawEmitter } = handlers;
|
|
185431
185431
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
185432
185432
|
const copts = cont && cont.options;
|
|
@@ -185448,7 +185448,7 @@ var setFsWatchFileListener = (path22, fullPath, options2, handlers) => {
|
|
|
185448
185448
|
});
|
|
185449
185449
|
const currmtime = curr.mtimeMs;
|
|
185450
185450
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
185451
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
185451
|
+
foreach(cont.listeners, (listener2) => listener2(path23, curr));
|
|
185452
185452
|
}
|
|
185453
185453
|
})
|
|
185454
185454
|
};
|
|
@@ -185478,13 +185478,13 @@ var NodeFsHandler = class {
|
|
|
185478
185478
|
* @param listener on fs change
|
|
185479
185479
|
* @returns closer for the watcher instance
|
|
185480
185480
|
*/
|
|
185481
|
-
_watchWithNodeFs(
|
|
185481
|
+
_watchWithNodeFs(path23, listener) {
|
|
185482
185482
|
const opts = this.fsw.options;
|
|
185483
|
-
const directory = sp.dirname(
|
|
185484
|
-
const basename5 = sp.basename(
|
|
185483
|
+
const directory = sp.dirname(path23);
|
|
185484
|
+
const basename5 = sp.basename(path23);
|
|
185485
185485
|
const parent = this.fsw._getWatchedDir(directory);
|
|
185486
185486
|
parent.add(basename5);
|
|
185487
|
-
const absolutePath = sp.resolve(
|
|
185487
|
+
const absolutePath = sp.resolve(path23);
|
|
185488
185488
|
const options2 = {
|
|
185489
185489
|
persistent: opts.persistent
|
|
185490
185490
|
};
|
|
@@ -185494,12 +185494,12 @@ var NodeFsHandler = class {
|
|
|
185494
185494
|
if (opts.usePolling) {
|
|
185495
185495
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
185496
185496
|
options2.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
|
|
185497
|
-
closer = setFsWatchFileListener(
|
|
185497
|
+
closer = setFsWatchFileListener(path23, absolutePath, options2, {
|
|
185498
185498
|
listener,
|
|
185499
185499
|
rawEmitter: this.fsw._emitRaw
|
|
185500
185500
|
});
|
|
185501
185501
|
} else {
|
|
185502
|
-
closer = setFsWatchListener(
|
|
185502
|
+
closer = setFsWatchListener(path23, absolutePath, options2, {
|
|
185503
185503
|
listener,
|
|
185504
185504
|
errHandler: this._boundHandleError,
|
|
185505
185505
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -185521,7 +185521,7 @@ var NodeFsHandler = class {
|
|
|
185521
185521
|
let prevStats = stats;
|
|
185522
185522
|
if (parent.has(basename5))
|
|
185523
185523
|
return;
|
|
185524
|
-
const listener = async (
|
|
185524
|
+
const listener = async (path23, newStats) => {
|
|
185525
185525
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
185526
185526
|
return;
|
|
185527
185527
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -185535,11 +185535,11 @@ var NodeFsHandler = class {
|
|
|
185535
185535
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
185536
185536
|
}
|
|
185537
185537
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
185538
|
-
this.fsw._closeFile(
|
|
185538
|
+
this.fsw._closeFile(path23);
|
|
185539
185539
|
prevStats = newStats2;
|
|
185540
185540
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
185541
185541
|
if (closer2)
|
|
185542
|
-
this.fsw._addPathCloser(
|
|
185542
|
+
this.fsw._addPathCloser(path23, closer2);
|
|
185543
185543
|
} else {
|
|
185544
185544
|
prevStats = newStats2;
|
|
185545
185545
|
}
|
|
@@ -185571,7 +185571,7 @@ var NodeFsHandler = class {
|
|
|
185571
185571
|
* @param item basename of this item
|
|
185572
185572
|
* @returns true if no more processing is needed for this entry.
|
|
185573
185573
|
*/
|
|
185574
|
-
async _handleSymlink(entry, directory,
|
|
185574
|
+
async _handleSymlink(entry, directory, path23, item) {
|
|
185575
185575
|
if (this.fsw.closed) {
|
|
185576
185576
|
return;
|
|
185577
185577
|
}
|
|
@@ -185581,7 +185581,7 @@ var NodeFsHandler = class {
|
|
|
185581
185581
|
this.fsw._incrReadyCount();
|
|
185582
185582
|
let linkPath;
|
|
185583
185583
|
try {
|
|
185584
|
-
linkPath = await fsrealpath(
|
|
185584
|
+
linkPath = await fsrealpath(path23);
|
|
185585
185585
|
} catch (e) {
|
|
185586
185586
|
this.fsw._emitReady();
|
|
185587
185587
|
return true;
|
|
@@ -185591,12 +185591,12 @@ var NodeFsHandler = class {
|
|
|
185591
185591
|
if (dir.has(item)) {
|
|
185592
185592
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
185593
185593
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
185594
|
-
this.fsw._emit(EV.CHANGE,
|
|
185594
|
+
this.fsw._emit(EV.CHANGE, path23, entry.stats);
|
|
185595
185595
|
}
|
|
185596
185596
|
} else {
|
|
185597
185597
|
dir.add(item);
|
|
185598
185598
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
185599
|
-
this.fsw._emit(EV.ADD,
|
|
185599
|
+
this.fsw._emit(EV.ADD, path23, entry.stats);
|
|
185600
185600
|
}
|
|
185601
185601
|
this.fsw._emitReady();
|
|
185602
185602
|
return true;
|
|
@@ -185626,9 +185626,9 @@ var NodeFsHandler = class {
|
|
|
185626
185626
|
return;
|
|
185627
185627
|
}
|
|
185628
185628
|
const item = entry.path;
|
|
185629
|
-
let
|
|
185629
|
+
let path23 = sp.join(directory, item);
|
|
185630
185630
|
current.add(item);
|
|
185631
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
185631
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path23, item)) {
|
|
185632
185632
|
return;
|
|
185633
185633
|
}
|
|
185634
185634
|
if (this.fsw.closed) {
|
|
@@ -185637,8 +185637,8 @@ var NodeFsHandler = class {
|
|
|
185637
185637
|
}
|
|
185638
185638
|
if (item === target || !target && !previous.has(item)) {
|
|
185639
185639
|
this.fsw._incrReadyCount();
|
|
185640
|
-
|
|
185641
|
-
this._addToNodeFs(
|
|
185640
|
+
path23 = sp.join(dir, sp.relative(dir, path23));
|
|
185641
|
+
this._addToNodeFs(path23, initialAdd, wh, depth + 1);
|
|
185642
185642
|
}
|
|
185643
185643
|
}).on(EV.ERROR, this._boundHandleError);
|
|
185644
185644
|
return new Promise((resolve7, reject) => {
|
|
@@ -185707,13 +185707,13 @@ var NodeFsHandler = class {
|
|
|
185707
185707
|
* @param depth Child path actually targeted for watch
|
|
185708
185708
|
* @param target Child path actually targeted for watch
|
|
185709
185709
|
*/
|
|
185710
|
-
async _addToNodeFs(
|
|
185710
|
+
async _addToNodeFs(path23, initialAdd, priorWh, depth, target) {
|
|
185711
185711
|
const ready = this.fsw._emitReady;
|
|
185712
|
-
if (this.fsw._isIgnored(
|
|
185712
|
+
if (this.fsw._isIgnored(path23) || this.fsw.closed) {
|
|
185713
185713
|
ready();
|
|
185714
185714
|
return false;
|
|
185715
185715
|
}
|
|
185716
|
-
const wh = this.fsw._getWatchHelpers(
|
|
185716
|
+
const wh = this.fsw._getWatchHelpers(path23);
|
|
185717
185717
|
if (priorWh) {
|
|
185718
185718
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
185719
185719
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -185729,8 +185729,8 @@ var NodeFsHandler = class {
|
|
|
185729
185729
|
const follow = this.fsw.options.followSymlinks;
|
|
185730
185730
|
let closer;
|
|
185731
185731
|
if (stats.isDirectory()) {
|
|
185732
|
-
const absPath = sp.resolve(
|
|
185733
|
-
const targetPath = follow ? await fsrealpath(
|
|
185732
|
+
const absPath = sp.resolve(path23);
|
|
185733
|
+
const targetPath = follow ? await fsrealpath(path23) : path23;
|
|
185734
185734
|
if (this.fsw.closed)
|
|
185735
185735
|
return;
|
|
185736
185736
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -185740,29 +185740,29 @@ var NodeFsHandler = class {
|
|
|
185740
185740
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
185741
185741
|
}
|
|
185742
185742
|
} else if (stats.isSymbolicLink()) {
|
|
185743
|
-
const targetPath = follow ? await fsrealpath(
|
|
185743
|
+
const targetPath = follow ? await fsrealpath(path23) : path23;
|
|
185744
185744
|
if (this.fsw.closed)
|
|
185745
185745
|
return;
|
|
185746
185746
|
const parent = sp.dirname(wh.watchPath);
|
|
185747
185747
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
185748
185748
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
185749
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
185749
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path23, wh, targetPath);
|
|
185750
185750
|
if (this.fsw.closed)
|
|
185751
185751
|
return;
|
|
185752
185752
|
if (targetPath !== void 0) {
|
|
185753
|
-
this.fsw._symlinkPaths.set(sp.resolve(
|
|
185753
|
+
this.fsw._symlinkPaths.set(sp.resolve(path23), targetPath);
|
|
185754
185754
|
}
|
|
185755
185755
|
} else {
|
|
185756
185756
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
185757
185757
|
}
|
|
185758
185758
|
ready();
|
|
185759
185759
|
if (closer)
|
|
185760
|
-
this.fsw._addPathCloser(
|
|
185760
|
+
this.fsw._addPathCloser(path23, closer);
|
|
185761
185761
|
return false;
|
|
185762
185762
|
} catch (error) {
|
|
185763
185763
|
if (this.fsw._handleError(error)) {
|
|
185764
185764
|
ready();
|
|
185765
|
-
return
|
|
185765
|
+
return path23;
|
|
185766
185766
|
}
|
|
185767
185767
|
}
|
|
185768
185768
|
}
|
|
@@ -185805,24 +185805,24 @@ function createPattern(matcher) {
|
|
|
185805
185805
|
}
|
|
185806
185806
|
return () => false;
|
|
185807
185807
|
}
|
|
185808
|
-
function normalizePath(
|
|
185809
|
-
if (typeof
|
|
185808
|
+
function normalizePath(path23) {
|
|
185809
|
+
if (typeof path23 !== "string")
|
|
185810
185810
|
throw new Error("string expected");
|
|
185811
|
-
|
|
185812
|
-
|
|
185811
|
+
path23 = sp2.normalize(path23);
|
|
185812
|
+
path23 = path23.replace(/\\/g, "/");
|
|
185813
185813
|
let prepend = false;
|
|
185814
|
-
if (
|
|
185814
|
+
if (path23.startsWith("//"))
|
|
185815
185815
|
prepend = true;
|
|
185816
|
-
|
|
185816
|
+
path23 = path23.replace(DOUBLE_SLASH_RE, "/");
|
|
185817
185817
|
if (prepend)
|
|
185818
|
-
|
|
185819
|
-
return
|
|
185818
|
+
path23 = "/" + path23;
|
|
185819
|
+
return path23;
|
|
185820
185820
|
}
|
|
185821
185821
|
function matchPatterns(patterns, testString, stats) {
|
|
185822
|
-
const
|
|
185822
|
+
const path23 = normalizePath(testString);
|
|
185823
185823
|
for (let index = 0; index < patterns.length; index++) {
|
|
185824
185824
|
const pattern = patterns[index];
|
|
185825
|
-
if (pattern(
|
|
185825
|
+
if (pattern(path23, stats)) {
|
|
185826
185826
|
return true;
|
|
185827
185827
|
}
|
|
185828
185828
|
}
|
|
@@ -185860,19 +185860,19 @@ var toUnix = (string) => {
|
|
|
185860
185860
|
}
|
|
185861
185861
|
return str;
|
|
185862
185862
|
};
|
|
185863
|
-
var normalizePathToUnix = (
|
|
185864
|
-
var normalizeIgnored = (cwd = "") => (
|
|
185865
|
-
if (typeof
|
|
185866
|
-
return normalizePathToUnix(sp2.isAbsolute(
|
|
185863
|
+
var normalizePathToUnix = (path23) => toUnix(sp2.normalize(toUnix(path23)));
|
|
185864
|
+
var normalizeIgnored = (cwd = "") => (path23) => {
|
|
185865
|
+
if (typeof path23 === "string") {
|
|
185866
|
+
return normalizePathToUnix(sp2.isAbsolute(path23) ? path23 : sp2.join(cwd, path23));
|
|
185867
185867
|
} else {
|
|
185868
|
-
return
|
|
185868
|
+
return path23;
|
|
185869
185869
|
}
|
|
185870
185870
|
};
|
|
185871
|
-
var getAbsolutePath = (
|
|
185872
|
-
if (sp2.isAbsolute(
|
|
185873
|
-
return
|
|
185871
|
+
var getAbsolutePath = (path23, cwd) => {
|
|
185872
|
+
if (sp2.isAbsolute(path23)) {
|
|
185873
|
+
return path23;
|
|
185874
185874
|
}
|
|
185875
|
-
return sp2.join(cwd,
|
|
185875
|
+
return sp2.join(cwd, path23);
|
|
185876
185876
|
};
|
|
185877
185877
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
185878
185878
|
var DirEntry = class {
|
|
@@ -185937,10 +185937,10 @@ var WatchHelper = class {
|
|
|
185937
185937
|
dirParts;
|
|
185938
185938
|
followSymlinks;
|
|
185939
185939
|
statMethod;
|
|
185940
|
-
constructor(
|
|
185940
|
+
constructor(path23, follow, fsw) {
|
|
185941
185941
|
this.fsw = fsw;
|
|
185942
|
-
const watchPath =
|
|
185943
|
-
this.path =
|
|
185942
|
+
const watchPath = path23;
|
|
185943
|
+
this.path = path23 = path23.replace(REPLACER_RE, "");
|
|
185944
185944
|
this.watchPath = watchPath;
|
|
185945
185945
|
this.fullWatchPath = sp2.resolve(watchPath);
|
|
185946
185946
|
this.dirParts = [];
|
|
@@ -186080,20 +186080,20 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186080
186080
|
this._closePromise = void 0;
|
|
186081
186081
|
let paths = unifyPaths(paths_);
|
|
186082
186082
|
if (cwd) {
|
|
186083
|
-
paths = paths.map((
|
|
186084
|
-
const absPath = getAbsolutePath(
|
|
186083
|
+
paths = paths.map((path23) => {
|
|
186084
|
+
const absPath = getAbsolutePath(path23, cwd);
|
|
186085
186085
|
return absPath;
|
|
186086
186086
|
});
|
|
186087
186087
|
}
|
|
186088
|
-
paths.forEach((
|
|
186089
|
-
this._removeIgnoredPath(
|
|
186088
|
+
paths.forEach((path23) => {
|
|
186089
|
+
this._removeIgnoredPath(path23);
|
|
186090
186090
|
});
|
|
186091
186091
|
this._userIgnored = void 0;
|
|
186092
186092
|
if (!this._readyCount)
|
|
186093
186093
|
this._readyCount = 0;
|
|
186094
186094
|
this._readyCount += paths.length;
|
|
186095
|
-
Promise.all(paths.map(async (
|
|
186096
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
186095
|
+
Promise.all(paths.map(async (path23) => {
|
|
186096
|
+
const res = await this._nodeFsHandler._addToNodeFs(path23, !_internal, void 0, 0, _origAdd);
|
|
186097
186097
|
if (res)
|
|
186098
186098
|
this._emitReady();
|
|
186099
186099
|
return res;
|
|
@@ -186115,17 +186115,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186115
186115
|
return this;
|
|
186116
186116
|
const paths = unifyPaths(paths_);
|
|
186117
186117
|
const { cwd } = this.options;
|
|
186118
|
-
paths.forEach((
|
|
186119
|
-
if (!sp2.isAbsolute(
|
|
186118
|
+
paths.forEach((path23) => {
|
|
186119
|
+
if (!sp2.isAbsolute(path23) && !this._closers.has(path23)) {
|
|
186120
186120
|
if (cwd)
|
|
186121
|
-
|
|
186122
|
-
|
|
186121
|
+
path23 = sp2.join(cwd, path23);
|
|
186122
|
+
path23 = sp2.resolve(path23);
|
|
186123
186123
|
}
|
|
186124
|
-
this._closePath(
|
|
186125
|
-
this._addIgnoredPath(
|
|
186126
|
-
if (this._watched.has(
|
|
186124
|
+
this._closePath(path23);
|
|
186125
|
+
this._addIgnoredPath(path23);
|
|
186126
|
+
if (this._watched.has(path23)) {
|
|
186127
186127
|
this._addIgnoredPath({
|
|
186128
|
-
path:
|
|
186128
|
+
path: path23,
|
|
186129
186129
|
recursive: true
|
|
186130
186130
|
});
|
|
186131
186131
|
}
|
|
@@ -186189,38 +186189,38 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186189
186189
|
* @param stats arguments to be passed with event
|
|
186190
186190
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
186191
186191
|
*/
|
|
186192
|
-
async _emit(event,
|
|
186192
|
+
async _emit(event, path23, stats) {
|
|
186193
186193
|
if (this.closed)
|
|
186194
186194
|
return;
|
|
186195
186195
|
const opts = this.options;
|
|
186196
186196
|
if (isWindows)
|
|
186197
|
-
|
|
186197
|
+
path23 = sp2.normalize(path23);
|
|
186198
186198
|
if (opts.cwd)
|
|
186199
|
-
|
|
186200
|
-
const args = [
|
|
186199
|
+
path23 = sp2.relative(opts.cwd, path23);
|
|
186200
|
+
const args = [path23];
|
|
186201
186201
|
if (stats != null)
|
|
186202
186202
|
args.push(stats);
|
|
186203
186203
|
const awf = opts.awaitWriteFinish;
|
|
186204
186204
|
let pw;
|
|
186205
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
186205
|
+
if (awf && (pw = this._pendingWrites.get(path23))) {
|
|
186206
186206
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
186207
186207
|
return this;
|
|
186208
186208
|
}
|
|
186209
186209
|
if (opts.atomic) {
|
|
186210
186210
|
if (event === EVENTS.UNLINK) {
|
|
186211
|
-
this._pendingUnlinks.set(
|
|
186211
|
+
this._pendingUnlinks.set(path23, [event, ...args]);
|
|
186212
186212
|
setTimeout(() => {
|
|
186213
|
-
this._pendingUnlinks.forEach((entry,
|
|
186213
|
+
this._pendingUnlinks.forEach((entry, path24) => {
|
|
186214
186214
|
this.emit(...entry);
|
|
186215
186215
|
this.emit(EVENTS.ALL, ...entry);
|
|
186216
|
-
this._pendingUnlinks.delete(
|
|
186216
|
+
this._pendingUnlinks.delete(path24);
|
|
186217
186217
|
});
|
|
186218
186218
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
186219
186219
|
return this;
|
|
186220
186220
|
}
|
|
186221
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
186221
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path23)) {
|
|
186222
186222
|
event = EVENTS.CHANGE;
|
|
186223
|
-
this._pendingUnlinks.delete(
|
|
186223
|
+
this._pendingUnlinks.delete(path23);
|
|
186224
186224
|
}
|
|
186225
186225
|
}
|
|
186226
186226
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -186238,16 +186238,16 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186238
186238
|
this.emitWithAll(event, args);
|
|
186239
186239
|
}
|
|
186240
186240
|
};
|
|
186241
|
-
this._awaitWriteFinish(
|
|
186241
|
+
this._awaitWriteFinish(path23, awf.stabilityThreshold, event, awfEmit);
|
|
186242
186242
|
return this;
|
|
186243
186243
|
}
|
|
186244
186244
|
if (event === EVENTS.CHANGE) {
|
|
186245
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
186245
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path23, 50);
|
|
186246
186246
|
if (isThrottled)
|
|
186247
186247
|
return this;
|
|
186248
186248
|
}
|
|
186249
186249
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
186250
|
-
const fullPath = opts.cwd ? sp2.join(opts.cwd,
|
|
186250
|
+
const fullPath = opts.cwd ? sp2.join(opts.cwd, path23) : path23;
|
|
186251
186251
|
let stats2;
|
|
186252
186252
|
try {
|
|
186253
186253
|
stats2 = await stat3(fullPath);
|
|
@@ -186278,23 +186278,23 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186278
186278
|
* @param timeout duration of time to suppress duplicate actions
|
|
186279
186279
|
* @returns tracking object or false if action should be suppressed
|
|
186280
186280
|
*/
|
|
186281
|
-
_throttle(actionType,
|
|
186281
|
+
_throttle(actionType, path23, timeout) {
|
|
186282
186282
|
if (!this._throttled.has(actionType)) {
|
|
186283
186283
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
186284
186284
|
}
|
|
186285
186285
|
const action = this._throttled.get(actionType);
|
|
186286
186286
|
if (!action)
|
|
186287
186287
|
throw new Error("invalid throttle");
|
|
186288
|
-
const actionPath = action.get(
|
|
186288
|
+
const actionPath = action.get(path23);
|
|
186289
186289
|
if (actionPath) {
|
|
186290
186290
|
actionPath.count++;
|
|
186291
186291
|
return false;
|
|
186292
186292
|
}
|
|
186293
186293
|
let timeoutObject;
|
|
186294
186294
|
const clear = () => {
|
|
186295
|
-
const item = action.get(
|
|
186295
|
+
const item = action.get(path23);
|
|
186296
186296
|
const count = item ? item.count : 0;
|
|
186297
|
-
action.delete(
|
|
186297
|
+
action.delete(path23);
|
|
186298
186298
|
clearTimeout(timeoutObject);
|
|
186299
186299
|
if (item)
|
|
186300
186300
|
clearTimeout(item.timeoutObject);
|
|
@@ -186302,7 +186302,7 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186302
186302
|
};
|
|
186303
186303
|
timeoutObject = setTimeout(clear, timeout);
|
|
186304
186304
|
const thr = { timeoutObject, clear, count: 0 };
|
|
186305
|
-
action.set(
|
|
186305
|
+
action.set(path23, thr);
|
|
186306
186306
|
return thr;
|
|
186307
186307
|
}
|
|
186308
186308
|
_incrReadyCount() {
|
|
@@ -186316,44 +186316,44 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186316
186316
|
* @param event
|
|
186317
186317
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
186318
186318
|
*/
|
|
186319
|
-
_awaitWriteFinish(
|
|
186319
|
+
_awaitWriteFinish(path23, threshold, event, awfEmit) {
|
|
186320
186320
|
const awf = this.options.awaitWriteFinish;
|
|
186321
186321
|
if (typeof awf !== "object")
|
|
186322
186322
|
return;
|
|
186323
186323
|
const pollInterval = awf.pollInterval;
|
|
186324
186324
|
let timeoutHandler;
|
|
186325
|
-
let fullPath =
|
|
186326
|
-
if (this.options.cwd && !sp2.isAbsolute(
|
|
186327
|
-
fullPath = sp2.join(this.options.cwd,
|
|
186325
|
+
let fullPath = path23;
|
|
186326
|
+
if (this.options.cwd && !sp2.isAbsolute(path23)) {
|
|
186327
|
+
fullPath = sp2.join(this.options.cwd, path23);
|
|
186328
186328
|
}
|
|
186329
186329
|
const now = /* @__PURE__ */ new Date();
|
|
186330
186330
|
const writes = this._pendingWrites;
|
|
186331
186331
|
function awaitWriteFinishFn(prevStat) {
|
|
186332
186332
|
statcb(fullPath, (err, curStat) => {
|
|
186333
|
-
if (err || !writes.has(
|
|
186333
|
+
if (err || !writes.has(path23)) {
|
|
186334
186334
|
if (err && err.code !== "ENOENT")
|
|
186335
186335
|
awfEmit(err);
|
|
186336
186336
|
return;
|
|
186337
186337
|
}
|
|
186338
186338
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
186339
186339
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
186340
|
-
writes.get(
|
|
186340
|
+
writes.get(path23).lastChange = now2;
|
|
186341
186341
|
}
|
|
186342
|
-
const pw = writes.get(
|
|
186342
|
+
const pw = writes.get(path23);
|
|
186343
186343
|
const df = now2 - pw.lastChange;
|
|
186344
186344
|
if (df >= threshold) {
|
|
186345
|
-
writes.delete(
|
|
186345
|
+
writes.delete(path23);
|
|
186346
186346
|
awfEmit(void 0, curStat);
|
|
186347
186347
|
} else {
|
|
186348
186348
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
186349
186349
|
}
|
|
186350
186350
|
});
|
|
186351
186351
|
}
|
|
186352
|
-
if (!writes.has(
|
|
186353
|
-
writes.set(
|
|
186352
|
+
if (!writes.has(path23)) {
|
|
186353
|
+
writes.set(path23, {
|
|
186354
186354
|
lastChange: now,
|
|
186355
186355
|
cancelWait: () => {
|
|
186356
|
-
writes.delete(
|
|
186356
|
+
writes.delete(path23);
|
|
186357
186357
|
clearTimeout(timeoutHandler);
|
|
186358
186358
|
return event;
|
|
186359
186359
|
}
|
|
@@ -186364,8 +186364,8 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186364
186364
|
/**
|
|
186365
186365
|
* Determines whether user has asked to ignore this path.
|
|
186366
186366
|
*/
|
|
186367
|
-
_isIgnored(
|
|
186368
|
-
if (this.options.atomic && DOT_RE.test(
|
|
186367
|
+
_isIgnored(path23, stats) {
|
|
186368
|
+
if (this.options.atomic && DOT_RE.test(path23))
|
|
186369
186369
|
return true;
|
|
186370
186370
|
if (!this._userIgnored) {
|
|
186371
186371
|
const { cwd } = this.options;
|
|
@@ -186375,17 +186375,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186375
186375
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
186376
186376
|
this._userIgnored = anymatch(list, void 0);
|
|
186377
186377
|
}
|
|
186378
|
-
return this._userIgnored(
|
|
186378
|
+
return this._userIgnored(path23, stats);
|
|
186379
186379
|
}
|
|
186380
|
-
_isntIgnored(
|
|
186381
|
-
return !this._isIgnored(
|
|
186380
|
+
_isntIgnored(path23, stat4) {
|
|
186381
|
+
return !this._isIgnored(path23, stat4);
|
|
186382
186382
|
}
|
|
186383
186383
|
/**
|
|
186384
186384
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
186385
186385
|
* @param path file or directory pattern being watched
|
|
186386
186386
|
*/
|
|
186387
|
-
_getWatchHelpers(
|
|
186388
|
-
return new WatchHelper(
|
|
186387
|
+
_getWatchHelpers(path23) {
|
|
186388
|
+
return new WatchHelper(path23, this.options.followSymlinks, this);
|
|
186389
186389
|
}
|
|
186390
186390
|
// Directory helpers
|
|
186391
186391
|
// -----------------
|
|
@@ -186417,63 +186417,63 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186417
186417
|
* @param item base path of item/directory
|
|
186418
186418
|
*/
|
|
186419
186419
|
_remove(directory, item, isDirectory) {
|
|
186420
|
-
const
|
|
186421
|
-
const fullPath = sp2.resolve(
|
|
186422
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
186423
|
-
if (!this._throttle("remove",
|
|
186420
|
+
const path23 = sp2.join(directory, item);
|
|
186421
|
+
const fullPath = sp2.resolve(path23);
|
|
186422
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path23) || this._watched.has(fullPath);
|
|
186423
|
+
if (!this._throttle("remove", path23, 100))
|
|
186424
186424
|
return;
|
|
186425
186425
|
if (!isDirectory && this._watched.size === 1) {
|
|
186426
186426
|
this.add(directory, item, true);
|
|
186427
186427
|
}
|
|
186428
|
-
const wp = this._getWatchedDir(
|
|
186428
|
+
const wp = this._getWatchedDir(path23);
|
|
186429
186429
|
const nestedDirectoryChildren = wp.getChildren();
|
|
186430
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
186430
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path23, nested));
|
|
186431
186431
|
const parent = this._getWatchedDir(directory);
|
|
186432
186432
|
const wasTracked = parent.has(item);
|
|
186433
186433
|
parent.remove(item);
|
|
186434
186434
|
if (this._symlinkPaths.has(fullPath)) {
|
|
186435
186435
|
this._symlinkPaths.delete(fullPath);
|
|
186436
186436
|
}
|
|
186437
|
-
let relPath =
|
|
186437
|
+
let relPath = path23;
|
|
186438
186438
|
if (this.options.cwd)
|
|
186439
|
-
relPath = sp2.relative(this.options.cwd,
|
|
186439
|
+
relPath = sp2.relative(this.options.cwd, path23);
|
|
186440
186440
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
186441
186441
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
186442
186442
|
if (event === EVENTS.ADD)
|
|
186443
186443
|
return;
|
|
186444
186444
|
}
|
|
186445
|
-
this._watched.delete(
|
|
186445
|
+
this._watched.delete(path23);
|
|
186446
186446
|
this._watched.delete(fullPath);
|
|
186447
186447
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
186448
|
-
if (wasTracked && !this._isIgnored(
|
|
186449
|
-
this._emit(eventName,
|
|
186450
|
-
this._closePath(
|
|
186448
|
+
if (wasTracked && !this._isIgnored(path23))
|
|
186449
|
+
this._emit(eventName, path23);
|
|
186450
|
+
this._closePath(path23);
|
|
186451
186451
|
}
|
|
186452
186452
|
/**
|
|
186453
186453
|
* Closes all watchers for a path
|
|
186454
186454
|
*/
|
|
186455
|
-
_closePath(
|
|
186456
|
-
this._closeFile(
|
|
186457
|
-
const dir = sp2.dirname(
|
|
186458
|
-
this._getWatchedDir(dir).remove(sp2.basename(
|
|
186455
|
+
_closePath(path23) {
|
|
186456
|
+
this._closeFile(path23);
|
|
186457
|
+
const dir = sp2.dirname(path23);
|
|
186458
|
+
this._getWatchedDir(dir).remove(sp2.basename(path23));
|
|
186459
186459
|
}
|
|
186460
186460
|
/**
|
|
186461
186461
|
* Closes only file-specific watchers
|
|
186462
186462
|
*/
|
|
186463
|
-
_closeFile(
|
|
186464
|
-
const closers = this._closers.get(
|
|
186463
|
+
_closeFile(path23) {
|
|
186464
|
+
const closers = this._closers.get(path23);
|
|
186465
186465
|
if (!closers)
|
|
186466
186466
|
return;
|
|
186467
186467
|
closers.forEach((closer) => closer());
|
|
186468
|
-
this._closers.delete(
|
|
186468
|
+
this._closers.delete(path23);
|
|
186469
186469
|
}
|
|
186470
|
-
_addPathCloser(
|
|
186470
|
+
_addPathCloser(path23, closer) {
|
|
186471
186471
|
if (!closer)
|
|
186472
186472
|
return;
|
|
186473
|
-
let list = this._closers.get(
|
|
186473
|
+
let list = this._closers.get(path23);
|
|
186474
186474
|
if (!list) {
|
|
186475
186475
|
list = [];
|
|
186476
|
-
this._closers.set(
|
|
186476
|
+
this._closers.set(path23, list);
|
|
186477
186477
|
}
|
|
186478
186478
|
list.push(closer);
|
|
186479
186479
|
}
|
|
@@ -188620,10 +188620,108 @@ function watchConfigFile(configPath, debounceMs, onChange) {
|
|
|
188620
188620
|
};
|
|
188621
188621
|
}
|
|
188622
188622
|
|
|
188623
|
-
// src/lib/dev/
|
|
188623
|
+
// src/lib/dev/subdomain-generator.ts
|
|
188624
188624
|
import * as fs18 from "fs";
|
|
188625
188625
|
import * as path15 from "path";
|
|
188626
|
+
import { generateSlug } from "random-word-slugs";
|
|
188627
|
+
var StableSubdomainAllocator = class {
|
|
188628
|
+
tunnelsDir;
|
|
188629
|
+
tunnelsFilePath;
|
|
188630
|
+
baseSlug = null;
|
|
188631
|
+
constructor(projectRoot, key = "default") {
|
|
188632
|
+
this.tunnelsDir = path15.join(projectRoot, ".specific", "keys", key);
|
|
188633
|
+
this.tunnelsFilePath = path15.join(this.tunnelsDir, "tunnels.json");
|
|
188634
|
+
this.loadTunnels();
|
|
188635
|
+
}
|
|
188636
|
+
loadTunnels() {
|
|
188637
|
+
if (!fs18.existsSync(this.tunnelsFilePath)) {
|
|
188638
|
+
return;
|
|
188639
|
+
}
|
|
188640
|
+
try {
|
|
188641
|
+
const content = fs18.readFileSync(this.tunnelsFilePath, "utf-8");
|
|
188642
|
+
const data = JSON.parse(content);
|
|
188643
|
+
if (data.version === 1 && data.baseSlug) {
|
|
188644
|
+
this.baseSlug = data.baseSlug;
|
|
188645
|
+
}
|
|
188646
|
+
} catch {
|
|
188647
|
+
this.baseSlug = null;
|
|
188648
|
+
}
|
|
188649
|
+
}
|
|
188650
|
+
saveTunnels() {
|
|
188651
|
+
if (!fs18.existsSync(this.tunnelsDir)) {
|
|
188652
|
+
fs18.mkdirSync(this.tunnelsDir, { recursive: true });
|
|
188653
|
+
}
|
|
188654
|
+
const data = {
|
|
188655
|
+
version: 1,
|
|
188656
|
+
baseSlug: this.baseSlug
|
|
188657
|
+
};
|
|
188658
|
+
fs18.writeFileSync(this.tunnelsFilePath, JSON.stringify(data, null, 2));
|
|
188659
|
+
}
|
|
188660
|
+
generateBaseSlug() {
|
|
188661
|
+
return generateSlug(2, {
|
|
188662
|
+
format: "kebab",
|
|
188663
|
+
partsOfSpeech: ["adjective", "noun"],
|
|
188664
|
+
categories: {
|
|
188665
|
+
adjective: ["color", "appearance"],
|
|
188666
|
+
noun: ["animals"]
|
|
188667
|
+
}
|
|
188668
|
+
});
|
|
188669
|
+
}
|
|
188670
|
+
/**
|
|
188671
|
+
* Get the base slug, generating one if needed.
|
|
188672
|
+
*/
|
|
188673
|
+
getBaseSlug() {
|
|
188674
|
+
if (!this.baseSlug) {
|
|
188675
|
+
this.baseSlug = this.generateBaseSlug();
|
|
188676
|
+
this.saveTunnels();
|
|
188677
|
+
}
|
|
188678
|
+
return this.baseSlug;
|
|
188679
|
+
}
|
|
188680
|
+
/**
|
|
188681
|
+
* Allocate a subdomain for a service.
|
|
188682
|
+
* If multipleServices is true, appends the service name to the base slug.
|
|
188683
|
+
*/
|
|
188684
|
+
allocate(serviceName, multipleServices) {
|
|
188685
|
+
const baseSlug = this.getBaseSlug();
|
|
188686
|
+
if (multipleServices) {
|
|
188687
|
+
return `${baseSlug}-${serviceName}`;
|
|
188688
|
+
}
|
|
188689
|
+
return baseSlug;
|
|
188690
|
+
}
|
|
188691
|
+
};
|
|
188692
|
+
|
|
188693
|
+
// src/lib/dev/tunnel-manager.ts
|
|
188694
|
+
import localtunnel from "localtunnel";
|
|
188695
|
+
var TUNNEL_HOST = "https://tunnel.spcf.app";
|
|
188696
|
+
async function startTunnel(serviceName, endpointName, port, subdomain, callbacks) {
|
|
188697
|
+
const tunnel = await localtunnel({
|
|
188698
|
+
port,
|
|
188699
|
+
subdomain,
|
|
188700
|
+
host: TUNNEL_HOST
|
|
188701
|
+
});
|
|
188702
|
+
tunnel.on("error", (err) => {
|
|
188703
|
+
callbacks?.onError?.(serviceName, endpointName, err);
|
|
188704
|
+
});
|
|
188705
|
+
tunnel.on("close", () => {
|
|
188706
|
+
callbacks?.onClose?.(serviceName, endpointName);
|
|
188707
|
+
});
|
|
188708
|
+
return {
|
|
188709
|
+
serviceName,
|
|
188710
|
+
endpointName,
|
|
188711
|
+
localPort: port,
|
|
188712
|
+
url: tunnel.url,
|
|
188713
|
+
subdomain,
|
|
188714
|
+
stop: async () => {
|
|
188715
|
+
tunnel.close();
|
|
188716
|
+
}
|
|
188717
|
+
};
|
|
188718
|
+
}
|
|
188719
|
+
|
|
188720
|
+
// src/lib/dev/proxy-registry.ts
|
|
188721
|
+
import * as fs19 from "fs";
|
|
188722
|
+
import * as path16 from "path";
|
|
188626
188723
|
import * as os8 from "os";
|
|
188724
|
+
import * as net4 from "net";
|
|
188627
188725
|
var ProxyRegistryManager = class {
|
|
188628
188726
|
proxyDir;
|
|
188629
188727
|
ownerPath;
|
|
@@ -188632,14 +188730,14 @@ var ProxyRegistryManager = class {
|
|
|
188632
188730
|
isOwner = false;
|
|
188633
188731
|
registryWatcher = null;
|
|
188634
188732
|
constructor() {
|
|
188635
|
-
this.proxyDir =
|
|
188636
|
-
this.ownerPath =
|
|
188637
|
-
this.registryPath =
|
|
188638
|
-
this.lockPath =
|
|
188733
|
+
this.proxyDir = path16.join(os8.homedir(), ".specific", "proxy");
|
|
188734
|
+
this.ownerPath = path16.join(this.proxyDir, "owner.json");
|
|
188735
|
+
this.registryPath = path16.join(this.proxyDir, "registry.json");
|
|
188736
|
+
this.lockPath = path16.join(this.proxyDir, "registry.lock");
|
|
188639
188737
|
}
|
|
188640
188738
|
ensureProxyDir() {
|
|
188641
|
-
if (!
|
|
188642
|
-
|
|
188739
|
+
if (!fs19.existsSync(this.proxyDir)) {
|
|
188740
|
+
fs19.mkdirSync(this.proxyDir, { recursive: true });
|
|
188643
188741
|
}
|
|
188644
188742
|
}
|
|
188645
188743
|
isProcessRunning(pid) {
|
|
@@ -188651,20 +188749,60 @@ var ProxyRegistryManager = class {
|
|
|
188651
188749
|
return err.code !== "ESRCH";
|
|
188652
188750
|
}
|
|
188653
188751
|
}
|
|
188752
|
+
/**
|
|
188753
|
+
* Check if the proxy is actually listening on its expected port.
|
|
188754
|
+
* This catches cases where the owner process is alive but the proxy has crashed.
|
|
188755
|
+
*/
|
|
188756
|
+
isProxyListening(port, timeoutMs = 1e3) {
|
|
188757
|
+
return new Promise((resolve7) => {
|
|
188758
|
+
const socket = new net4.Socket();
|
|
188759
|
+
let resolved = false;
|
|
188760
|
+
const cleanup = () => {
|
|
188761
|
+
if (!resolved) {
|
|
188762
|
+
resolved = true;
|
|
188763
|
+
socket.destroy();
|
|
188764
|
+
}
|
|
188765
|
+
};
|
|
188766
|
+
socket.setTimeout(timeoutMs);
|
|
188767
|
+
socket.on("connect", () => {
|
|
188768
|
+
cleanup();
|
|
188769
|
+
resolve7(true);
|
|
188770
|
+
});
|
|
188771
|
+
socket.on("timeout", () => {
|
|
188772
|
+
cleanup();
|
|
188773
|
+
resolve7(false);
|
|
188774
|
+
});
|
|
188775
|
+
socket.on("error", () => {
|
|
188776
|
+
cleanup();
|
|
188777
|
+
resolve7(false);
|
|
188778
|
+
});
|
|
188779
|
+
socket.connect(port, "127.0.0.1");
|
|
188780
|
+
});
|
|
188781
|
+
}
|
|
188782
|
+
/**
|
|
188783
|
+
* Check if the proxy owner is healthy (process running AND proxy listening).
|
|
188784
|
+
*/
|
|
188785
|
+
async isProxyOwnerHealthy(pid) {
|
|
188786
|
+
if (!this.isProcessRunning(pid)) {
|
|
188787
|
+
return false;
|
|
188788
|
+
}
|
|
188789
|
+
const isListening = await this.isProxyListening(443);
|
|
188790
|
+
return isListening;
|
|
188791
|
+
}
|
|
188654
188792
|
async acquireLock(timeoutMs = 5e3) {
|
|
188655
188793
|
this.ensureProxyDir();
|
|
188656
188794
|
const startTime = Date.now();
|
|
188657
188795
|
while (Date.now() - startTime < timeoutMs) {
|
|
188658
188796
|
try {
|
|
188659
|
-
const fd =
|
|
188797
|
+
const fd = fs19.openSync(
|
|
188660
188798
|
this.lockPath,
|
|
188661
|
-
|
|
188799
|
+
fs19.constants.O_CREAT | fs19.constants.O_EXCL | fs19.constants.O_WRONLY
|
|
188662
188800
|
);
|
|
188663
|
-
|
|
188664
|
-
|
|
188801
|
+
fs19.writeSync(fd, String(process.pid));
|
|
188802
|
+
fs19.closeSync(fd);
|
|
188665
188803
|
return () => {
|
|
188666
188804
|
try {
|
|
188667
|
-
|
|
188805
|
+
fs19.unlinkSync(this.lockPath);
|
|
188668
188806
|
} catch {
|
|
188669
188807
|
}
|
|
188670
188808
|
};
|
|
@@ -188673,16 +188811,16 @@ var ProxyRegistryManager = class {
|
|
|
188673
188811
|
if (err.code === "EEXIST") {
|
|
188674
188812
|
try {
|
|
188675
188813
|
const lockPid = parseInt(
|
|
188676
|
-
|
|
188814
|
+
fs19.readFileSync(this.lockPath, "utf-8").trim(),
|
|
188677
188815
|
10
|
|
188678
188816
|
);
|
|
188679
188817
|
if (!this.isProcessRunning(lockPid)) {
|
|
188680
|
-
|
|
188818
|
+
fs19.unlinkSync(this.lockPath);
|
|
188681
188819
|
continue;
|
|
188682
188820
|
}
|
|
188683
188821
|
} catch {
|
|
188684
188822
|
try {
|
|
188685
|
-
|
|
188823
|
+
fs19.unlinkSync(this.lockPath);
|
|
188686
188824
|
} catch {
|
|
188687
188825
|
}
|
|
188688
188826
|
continue;
|
|
@@ -188702,10 +188840,10 @@ var ProxyRegistryManager = class {
|
|
|
188702
188840
|
async claimProxyOwnership(key) {
|
|
188703
188841
|
const releaseLock = await this.acquireLock();
|
|
188704
188842
|
try {
|
|
188705
|
-
if (
|
|
188706
|
-
const content =
|
|
188843
|
+
if (fs19.existsSync(this.ownerPath)) {
|
|
188844
|
+
const content = fs19.readFileSync(this.ownerPath, "utf-8");
|
|
188707
188845
|
const ownerFile2 = JSON.parse(content);
|
|
188708
|
-
if (this.
|
|
188846
|
+
if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
|
|
188709
188847
|
return false;
|
|
188710
188848
|
}
|
|
188711
188849
|
}
|
|
@@ -188733,11 +188871,11 @@ var ProxyRegistryManager = class {
|
|
|
188733
188871
|
}
|
|
188734
188872
|
const releaseLock = await this.acquireLock();
|
|
188735
188873
|
try {
|
|
188736
|
-
if (
|
|
188737
|
-
const content =
|
|
188874
|
+
if (fs19.existsSync(this.ownerPath)) {
|
|
188875
|
+
const content = fs19.readFileSync(this.ownerPath, "utf-8");
|
|
188738
188876
|
const ownerFile = JSON.parse(content);
|
|
188739
188877
|
if (ownerFile.owner.pid === process.pid) {
|
|
188740
|
-
|
|
188878
|
+
fs19.unlinkSync(this.ownerPath);
|
|
188741
188879
|
}
|
|
188742
188880
|
}
|
|
188743
188881
|
this.isOwner = false;
|
|
@@ -188749,14 +188887,14 @@ var ProxyRegistryManager = class {
|
|
|
188749
188887
|
* Get the current proxy owner.
|
|
188750
188888
|
*/
|
|
188751
188889
|
async getProxyOwner() {
|
|
188752
|
-
if (!
|
|
188890
|
+
if (!fs19.existsSync(this.ownerPath)) {
|
|
188753
188891
|
return null;
|
|
188754
188892
|
}
|
|
188755
188893
|
const releaseLock = await this.acquireLock();
|
|
188756
188894
|
try {
|
|
188757
|
-
const content =
|
|
188895
|
+
const content = fs19.readFileSync(this.ownerPath, "utf-8");
|
|
188758
188896
|
const ownerFile = JSON.parse(content);
|
|
188759
|
-
if (!this.
|
|
188897
|
+
if (!await this.isProxyOwnerHealthy(ownerFile.owner.pid)) {
|
|
188760
188898
|
return null;
|
|
188761
188899
|
}
|
|
188762
188900
|
return ownerFile.owner;
|
|
@@ -188848,7 +188986,7 @@ var ProxyRegistryManager = class {
|
|
|
188848
188986
|
*/
|
|
188849
188987
|
watchRegistry(onChange) {
|
|
188850
188988
|
this.ensureProxyDir();
|
|
188851
|
-
if (!
|
|
188989
|
+
if (!fs19.existsSync(this.registryPath)) {
|
|
188852
188990
|
const emptyRegistry = {
|
|
188853
188991
|
version: 1,
|
|
188854
188992
|
keys: {},
|
|
@@ -188892,13 +189030,13 @@ var ProxyRegistryManager = class {
|
|
|
188892
189030
|
async attemptElection(key) {
|
|
188893
189031
|
const releaseLock = await this.acquireLock();
|
|
188894
189032
|
try {
|
|
188895
|
-
if (
|
|
188896
|
-
const content =
|
|
189033
|
+
if (fs19.existsSync(this.ownerPath)) {
|
|
189034
|
+
const content = fs19.readFileSync(this.ownerPath, "utf-8");
|
|
188897
189035
|
const ownerFile2 = JSON.parse(content);
|
|
188898
|
-
if (this.
|
|
189036
|
+
if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
|
|
188899
189037
|
return false;
|
|
188900
189038
|
}
|
|
188901
|
-
|
|
189039
|
+
fs19.unlinkSync(this.ownerPath);
|
|
188902
189040
|
}
|
|
188903
189041
|
const ownerFile = {
|
|
188904
189042
|
version: 1,
|
|
@@ -188916,7 +189054,7 @@ var ProxyRegistryManager = class {
|
|
|
188916
189054
|
}
|
|
188917
189055
|
}
|
|
188918
189056
|
readRegistry() {
|
|
188919
|
-
if (!
|
|
189057
|
+
if (!fs19.existsSync(this.registryPath)) {
|
|
188920
189058
|
return {
|
|
188921
189059
|
version: 1,
|
|
188922
189060
|
keys: {},
|
|
@@ -188924,7 +189062,7 @@ var ProxyRegistryManager = class {
|
|
|
188924
189062
|
};
|
|
188925
189063
|
}
|
|
188926
189064
|
try {
|
|
188927
|
-
const content =
|
|
189065
|
+
const content = fs19.readFileSync(this.registryPath, "utf-8");
|
|
188928
189066
|
return JSON.parse(content);
|
|
188929
189067
|
} catch {
|
|
188930
189068
|
return {
|
|
@@ -188937,8 +189075,8 @@ var ProxyRegistryManager = class {
|
|
|
188937
189075
|
writeFileAtomic(filePath, data) {
|
|
188938
189076
|
this.ensureProxyDir();
|
|
188939
189077
|
const tmpPath = filePath + ".tmp";
|
|
188940
|
-
|
|
188941
|
-
|
|
189078
|
+
fs19.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
|
|
189079
|
+
fs19.renameSync(tmpPath, filePath);
|
|
188942
189080
|
}
|
|
188943
189081
|
};
|
|
188944
189082
|
|
|
@@ -189044,10 +189182,10 @@ function isInteractive() {
|
|
|
189044
189182
|
|
|
189045
189183
|
// src/commands/dev.tsx
|
|
189046
189184
|
var COLORS = ["cyan", "yellow", "green", "magenta", "blue"];
|
|
189047
|
-
function DevUI({ instanceKey }) {
|
|
189185
|
+
function DevUI({ instanceKey, tunnelEnabled }) {
|
|
189048
189186
|
const { exit } = useApp2();
|
|
189049
189187
|
const [state, setState] = useState5(() => {
|
|
189050
|
-
const caExists = caFilesExist();
|
|
189188
|
+
const caExists = tunnelEnabled || caFilesExist();
|
|
189051
189189
|
return {
|
|
189052
189190
|
status: caExists ? "loading" : "installing-ca",
|
|
189053
189191
|
...caExists ? {} : { caInstallPhase: "installing" },
|
|
@@ -189056,7 +189194,9 @@ function DevUI({ instanceKey }) {
|
|
|
189056
189194
|
services: [],
|
|
189057
189195
|
output: [],
|
|
189058
189196
|
colorMap: /* @__PURE__ */ new Map(),
|
|
189059
|
-
isProxyOwner: false
|
|
189197
|
+
isProxyOwner: false,
|
|
189198
|
+
tunnels: /* @__PURE__ */ new Map(),
|
|
189199
|
+
tunnelStatus: /* @__PURE__ */ new Map()
|
|
189060
189200
|
};
|
|
189061
189201
|
});
|
|
189062
189202
|
useEffect3(() => {
|
|
@@ -189088,6 +189228,7 @@ function DevUI({ instanceKey }) {
|
|
|
189088
189228
|
const drizzleGatewayRef = useRef(null);
|
|
189089
189229
|
const registryWatcherCleanupRef = useRef(null);
|
|
189090
189230
|
const electionIntervalRef = useRef(null);
|
|
189231
|
+
const tunnelsRef = useRef([]);
|
|
189091
189232
|
const proxyRef = useRef(null);
|
|
189092
189233
|
const adminServerRef = useRef(null);
|
|
189093
189234
|
const servicesRef = useRef([]);
|
|
@@ -189121,7 +189262,9 @@ function DevUI({ instanceKey }) {
|
|
|
189121
189262
|
// Stop Drizzle Gateway
|
|
189122
189263
|
drizzleGatewayRef.current?.stop(),
|
|
189123
189264
|
// Stop all resources
|
|
189124
|
-
...[...resourcesRef.current.values()].map((resource) => resource.stop())
|
|
189265
|
+
...[...resourcesRef.current.values()].map((resource) => resource.stop()),
|
|
189266
|
+
// Stop all tunnels
|
|
189267
|
+
...tunnelsRef.current.map((tunnel) => tunnel.stop())
|
|
189125
189268
|
]);
|
|
189126
189269
|
if (proxyRegistryRef.current) {
|
|
189127
189270
|
await proxyRegistryRef.current.unregisterServices(instanceKey);
|
|
@@ -189158,7 +189301,9 @@ function DevUI({ instanceKey }) {
|
|
|
189158
189301
|
// Stop Drizzle Gateway
|
|
189159
189302
|
drizzleGatewayRef.current?.stop(),
|
|
189160
189303
|
// Stop all resources
|
|
189161
|
-
...[...resourcesRef.current.values()].map((resource) => resource.stop())
|
|
189304
|
+
...[...resourcesRef.current.values()].map((resource) => resource.stop()),
|
|
189305
|
+
// Stop all tunnels
|
|
189306
|
+
...tunnelsRef.current.map((tunnel) => tunnel.stop())
|
|
189162
189307
|
]);
|
|
189163
189308
|
electricInstancesRef.current = [];
|
|
189164
189309
|
drizzleGatewayRef.current = null;
|
|
@@ -189166,6 +189311,7 @@ function DevUI({ instanceKey }) {
|
|
|
189166
189311
|
adminServerRef.current = null;
|
|
189167
189312
|
servicesRef.current = [];
|
|
189168
189313
|
resourcesRef.current = /* @__PURE__ */ new Map();
|
|
189314
|
+
tunnelsRef.current = [];
|
|
189169
189315
|
if (proxyRegistryRef.current) {
|
|
189170
189316
|
await proxyRegistryRef.current.unregisterServices(instanceKey);
|
|
189171
189317
|
await proxyRegistryRef.current.releaseProxyOwnership();
|
|
@@ -189181,7 +189327,9 @@ function DevUI({ instanceKey }) {
|
|
|
189181
189327
|
resourceStatus: /* @__PURE__ */ new Map(),
|
|
189182
189328
|
services: [],
|
|
189183
189329
|
proxy: void 0,
|
|
189184
|
-
isProxyOwner: false
|
|
189330
|
+
isProxyOwner: false,
|
|
189331
|
+
tunnels: /* @__PURE__ */ new Map(),
|
|
189332
|
+
tunnelStatus: /* @__PURE__ */ new Map()
|
|
189185
189333
|
}));
|
|
189186
189334
|
setReloadTrigger((t) => t + 1);
|
|
189187
189335
|
};
|
|
@@ -189222,10 +189370,10 @@ function DevUI({ instanceKey }) {
|
|
|
189222
189370
|
}, [state.status]);
|
|
189223
189371
|
useEffect3(() => {
|
|
189224
189372
|
if (state.status !== "running") return;
|
|
189225
|
-
const configPath =
|
|
189373
|
+
const configPath = path17.join(process.cwd(), "specific.hcl");
|
|
189226
189374
|
const watcher = watchConfigFile(configPath, 1e3, () => {
|
|
189227
189375
|
try {
|
|
189228
|
-
const hcl =
|
|
189376
|
+
const hcl = fs20.readFileSync(configPath, "utf-8");
|
|
189229
189377
|
parseConfig(hcl).then(() => {
|
|
189230
189378
|
triggerReload();
|
|
189231
189379
|
}).catch((err) => {
|
|
@@ -189350,8 +189498,8 @@ function DevUI({ instanceKey }) {
|
|
|
189350
189498
|
}));
|
|
189351
189499
|
return;
|
|
189352
189500
|
}
|
|
189353
|
-
const configPath =
|
|
189354
|
-
if (!
|
|
189501
|
+
const configPath = path17.join(process.cwd(), "specific.hcl");
|
|
189502
|
+
if (!fs20.existsSync(configPath)) {
|
|
189355
189503
|
writeLog("system", "Waiting for specific.hcl to appear");
|
|
189356
189504
|
setState((s) => ({
|
|
189357
189505
|
...s,
|
|
@@ -189370,7 +189518,7 @@ function DevUI({ instanceKey }) {
|
|
|
189370
189518
|
}
|
|
189371
189519
|
let config2;
|
|
189372
189520
|
try {
|
|
189373
|
-
const hcl =
|
|
189521
|
+
const hcl = fs20.readFileSync(configPath, "utf-8");
|
|
189374
189522
|
config2 = await parseConfig(hcl);
|
|
189375
189523
|
} catch (err) {
|
|
189376
189524
|
setState((s) => ({
|
|
@@ -189476,7 +189624,7 @@ function DevUI({ instanceKey }) {
|
|
|
189476
189624
|
const drizzleGateway = await startDrizzleGateway(
|
|
189477
189625
|
postgresResources,
|
|
189478
189626
|
drizzlePort,
|
|
189479
|
-
|
|
189627
|
+
path17.join(process.cwd(), ".specific", "keys", instanceKey)
|
|
189480
189628
|
);
|
|
189481
189629
|
startedDrizzleGateway = drizzleGateway;
|
|
189482
189630
|
drizzleGatewayRef.current = drizzleGateway;
|
|
@@ -189645,136 +189793,196 @@ Add them to the config block in specific.local`);
|
|
|
189645
189793
|
}
|
|
189646
189794
|
}
|
|
189647
189795
|
}
|
|
189648
|
-
|
|
189649
|
-
|
|
189650
|
-
|
|
189651
|
-
|
|
189652
|
-
|
|
189653
|
-
|
|
189654
|
-
|
|
189655
|
-
|
|
189656
|
-
|
|
189657
|
-
|
|
189658
|
-
|
|
189659
|
-
|
|
189660
|
-
|
|
189661
|
-
|
|
189662
|
-
|
|
189663
|
-
|
|
189664
|
-
|
|
189665
|
-
|
|
189666
|
-
|
|
189667
|
-
|
|
189668
|
-
|
|
189669
|
-
|
|
189670
|
-
|
|
189671
|
-
|
|
189672
|
-
|
|
189673
|
-
|
|
189674
|
-
|
|
189675
|
-
|
|
189676
|
-
|
|
189677
|
-
|
|
189678
|
-
|
|
189679
|
-
|
|
189680
|
-
|
|
189681
|
-
|
|
189682
|
-
|
|
189683
|
-
|
|
189684
|
-
|
|
189685
|
-
|
|
189686
|
-
|
|
189687
|
-
|
|
189688
|
-
|
|
189689
|
-
|
|
189690
|
-
|
|
189691
|
-
|
|
189692
|
-
|
|
189693
|
-
|
|
189694
|
-
|
|
189695
|
-
setState((s) => ({ ...s, proxy: proxy2, isProxyOwner: true }));
|
|
189696
|
-
const knownKeys = new Set(registeredKeys);
|
|
189697
|
-
registryWatcherCleanupRef.current = proxyRegistry.watchRegistry(async (updatedServices, updatedKeys) => {
|
|
189698
|
-
writeLog("system", `Registry updated: ${updatedServices.length} services`);
|
|
189699
|
-
proxy2.updateServices(updatedServices, updatedKeys);
|
|
189700
|
-
const newKeyNames = Object.keys(updatedKeys).filter((k) => !knownKeys.has(k));
|
|
189701
|
-
if (newKeyNames.length > 0) {
|
|
189702
|
-
writeLog("system", `New keys detected: ${newKeyNames.join(", ")} - regenerating certificate`);
|
|
189703
|
-
for (const key of newKeyNames) {
|
|
189704
|
-
knownKeys.add(key);
|
|
189796
|
+
if (tunnelEnabled) {
|
|
189797
|
+
writeLog("system", "Tunnel mode enabled, starting tunnels for public services");
|
|
189798
|
+
if (exposedServices.length === 0) {
|
|
189799
|
+
writeLog("system", "No public services to tunnel");
|
|
189800
|
+
} else {
|
|
189801
|
+
const subdomainAllocator = new StableSubdomainAllocator(process.cwd(), instanceKey);
|
|
189802
|
+
const multipleServices = exposedServices.length > 1;
|
|
189803
|
+
const tunnelInstances = [];
|
|
189804
|
+
const tunnelStatusMap = /* @__PURE__ */ new Map();
|
|
189805
|
+
for (const svc of exposedServices) {
|
|
189806
|
+
tunnelStatusMap.set(svc.name, "connecting");
|
|
189807
|
+
}
|
|
189808
|
+
setState((s) => ({ ...s, tunnelStatus: new Map(tunnelStatusMap) }));
|
|
189809
|
+
for (const svc of exposedServices) {
|
|
189810
|
+
if (cancelled) return;
|
|
189811
|
+
const subdomain = subdomainAllocator.allocate(svc.name, multipleServices);
|
|
189812
|
+
writeLog("system", `Starting tunnel for ${svc.name} on port ${svc.port} (subdomain: ${subdomain})`);
|
|
189813
|
+
try {
|
|
189814
|
+
const tunnel = await startTunnel(
|
|
189815
|
+
svc.name,
|
|
189816
|
+
"default",
|
|
189817
|
+
svc.port,
|
|
189818
|
+
subdomain,
|
|
189819
|
+
{
|
|
189820
|
+
onError: (serviceName, _endpointName, error) => {
|
|
189821
|
+
writeLog("system:error", `Tunnel error for ${serviceName}: ${error.message}`);
|
|
189822
|
+
},
|
|
189823
|
+
onClose: (serviceName) => {
|
|
189824
|
+
writeLog("system", `Tunnel closed for ${serviceName}`);
|
|
189825
|
+
}
|
|
189826
|
+
}
|
|
189827
|
+
);
|
|
189828
|
+
tunnelInstances.push(tunnel);
|
|
189829
|
+
tunnelsRef.current = [...tunnelInstances];
|
|
189830
|
+
tunnelStatusMap.set(svc.name, "connected");
|
|
189831
|
+
setState((s) => ({
|
|
189832
|
+
...s,
|
|
189833
|
+
tunnels: new Map([...s.tunnels, [svc.name, tunnel]]),
|
|
189834
|
+
tunnelStatus: new Map(tunnelStatusMap)
|
|
189835
|
+
}));
|
|
189836
|
+
writeLog("system", `Tunnel ready for ${svc.name}: ${tunnel.url}`);
|
|
189837
|
+
} catch (err) {
|
|
189838
|
+
const errorMsg = `Failed to start tunnel for ${svc.name}: ${err instanceof Error ? err.message : String(err)}`;
|
|
189839
|
+
writeLog("system:error", errorMsg);
|
|
189840
|
+
tunnelStatusMap.set(svc.name, "error");
|
|
189841
|
+
for (const t of tunnelInstances) {
|
|
189842
|
+
await t.stop();
|
|
189705
189843
|
}
|
|
189706
|
-
|
|
189707
|
-
|
|
189708
|
-
|
|
189844
|
+
setState((s) => ({
|
|
189845
|
+
...s,
|
|
189846
|
+
status: "error",
|
|
189847
|
+
error: errorMsg,
|
|
189848
|
+
tunnelStatus: new Map(tunnelStatusMap)
|
|
189849
|
+
}));
|
|
189850
|
+
return;
|
|
189709
189851
|
}
|
|
189710
|
-
}
|
|
189711
|
-
const currentKeys = await proxyRegistry.getAllKeyRegistrations();
|
|
189712
|
-
proxy2.updateServices(currentServices, currentKeys);
|
|
189713
|
-
writeLog("system", `Loaded ${currentServices.length} services from registry`);
|
|
189714
|
-
} catch (err) {
|
|
189715
|
-
const errorMsg = `Failed to start HTTP proxy: ${err instanceof Error ? err.message : String(err)}`;
|
|
189716
|
-
writeLog("system:error", errorMsg);
|
|
189717
|
-
setState((s) => ({
|
|
189718
|
-
...s,
|
|
189719
|
-
status: "error",
|
|
189720
|
-
error: errorMsg
|
|
189721
|
-
}));
|
|
189722
|
-
return;
|
|
189852
|
+
}
|
|
189723
189853
|
}
|
|
189724
189854
|
} else {
|
|
189725
|
-
|
|
189726
|
-
|
|
189727
|
-
|
|
189855
|
+
const runningServicePorts = /* @__PURE__ */ new Map();
|
|
189856
|
+
for (const s of services2) {
|
|
189857
|
+
runningServicePorts.set(s.name, s.ports.get("default"));
|
|
189858
|
+
}
|
|
189859
|
+
const projectId = hasProjectId() ? readProjectId() : void 0;
|
|
189860
|
+
const getState = () => ({
|
|
189861
|
+
status: "running",
|
|
189862
|
+
services: config2.services.filter((svc) => runningServicePorts.has(svc.name) || svc.serve).map((svc) => ({
|
|
189863
|
+
name: svc.name,
|
|
189864
|
+
port: runningServicePorts.get(svc.name),
|
|
189865
|
+
exposed: !!svc.serve || svc.endpoints.some((e) => e.public),
|
|
189866
|
+
env: svc.env
|
|
189867
|
+
})),
|
|
189868
|
+
resources: [...resources2.entries()].map(([name, r]) => ({
|
|
189869
|
+
name,
|
|
189870
|
+
type: r.type,
|
|
189871
|
+
port: r.port,
|
|
189872
|
+
host: r.host,
|
|
189873
|
+
syncEnabled: r.type === "postgres" && syncDatabases.has(name)
|
|
189874
|
+
})),
|
|
189875
|
+
projectId
|
|
189876
|
+
});
|
|
189877
|
+
const adminServer = await startAdminServer(getState);
|
|
189878
|
+
adminServerRef.current = adminServer;
|
|
189879
|
+
writeLog("system", `Admin API server started on port ${adminServer.port}`);
|
|
189880
|
+
await proxyRegistry.registerServices(
|
|
189881
|
+
instanceKey,
|
|
189882
|
+
adminServer.port,
|
|
189883
|
+
serviceInfos,
|
|
189884
|
+
startedDrizzleGateway?.port
|
|
189885
|
+
);
|
|
189886
|
+
writeLog("system", `Registered ${serviceInfos.length} services with proxy registry`);
|
|
189887
|
+
const becameProxyOwner = await proxyRegistry.claimProxyOwnership(instanceKey);
|
|
189888
|
+
if (becameProxyOwner) {
|
|
189889
|
+
writeLog("system", "Claimed proxy ownership, starting HTTP proxy");
|
|
189728
189890
|
try {
|
|
189729
|
-
|
|
189730
|
-
|
|
189731
|
-
|
|
189732
|
-
|
|
189733
|
-
|
|
189734
|
-
|
|
189735
|
-
|
|
189736
|
-
|
|
189737
|
-
|
|
189738
|
-
|
|
189739
|
-
|
|
189740
|
-
}
|
|
189891
|
+
const currentServices = await proxyRegistry.getAllServices();
|
|
189892
|
+
const registeredKeys = [...new Set(currentServices.map((s) => s.key))];
|
|
189893
|
+
const certificate = generateCertificate("local.spcf.app", registeredKeys);
|
|
189894
|
+
const proxy2 = await startHttpProxy(
|
|
189895
|
+
exposedServices,
|
|
189896
|
+
certificate,
|
|
189897
|
+
getState,
|
|
189898
|
+
instanceKey
|
|
189899
|
+
);
|
|
189900
|
+
startedProxy = proxy2;
|
|
189901
|
+
proxyRef.current = proxy2;
|
|
189902
|
+
setState((s) => ({ ...s, proxy: proxy2, isProxyOwner: true }));
|
|
189903
|
+
const knownKeys = new Set(registeredKeys);
|
|
189904
|
+
registryWatcherCleanupRef.current = proxyRegistry.watchRegistry(async (updatedServices, updatedKeys) => {
|
|
189905
|
+
writeLog("system", `Registry updated: ${updatedServices.length} services`);
|
|
189906
|
+
proxy2.updateServices(updatedServices, updatedKeys);
|
|
189907
|
+
const newKeyNames = Object.keys(updatedKeys).filter((k) => !knownKeys.has(k));
|
|
189908
|
+
if (newKeyNames.length > 0) {
|
|
189909
|
+
writeLog("system", `New keys detected: ${newKeyNames.join(", ")} - regenerating certificate`);
|
|
189910
|
+
for (const key of newKeyNames) {
|
|
189911
|
+
knownKeys.add(key);
|
|
189912
|
+
}
|
|
189913
|
+
const allKeys = [...knownKeys];
|
|
189914
|
+
const newCertificate = generateCertificate("local.spcf.app", allKeys);
|
|
189915
|
+
proxy2.updateCertificate(newCertificate);
|
|
189916
|
+
}
|
|
189917
|
+
});
|
|
189918
|
+
const currentKeys = await proxyRegistry.getAllKeyRegistrations();
|
|
189919
|
+
proxy2.updateServices(currentServices, currentKeys);
|
|
189920
|
+
writeLog("system", `Loaded ${currentServices.length} services from registry`);
|
|
189921
|
+
} catch (err) {
|
|
189922
|
+
const errorMsg = `Failed to start HTTP proxy: ${err instanceof Error ? err.message : String(err)}`;
|
|
189923
|
+
writeLog("system:error", errorMsg);
|
|
189924
|
+
setState((s) => ({
|
|
189925
|
+
...s,
|
|
189926
|
+
status: "error",
|
|
189927
|
+
error: errorMsg
|
|
189928
|
+
}));
|
|
189741
189929
|
return;
|
|
189742
189930
|
}
|
|
189743
|
-
|
|
189744
|
-
|
|
189745
|
-
|
|
189746
|
-
|
|
189747
|
-
|
|
189748
|
-
|
|
189931
|
+
} else {
|
|
189932
|
+
writeLog("system", "Another instance owns the proxy, starting election watcher");
|
|
189933
|
+
setState((s) => ({ ...s, isProxyOwner: false }));
|
|
189934
|
+
const isProcessRunning = (pid) => {
|
|
189935
|
+
try {
|
|
189936
|
+
process.kill(pid, 0);
|
|
189937
|
+
return true;
|
|
189938
|
+
} catch {
|
|
189939
|
+
return false;
|
|
189940
|
+
}
|
|
189941
|
+
};
|
|
189942
|
+
electionIntervalRef.current = setInterval(async () => {
|
|
189943
|
+
if (cancelled || shuttingDown.current) {
|
|
189749
189944
|
if (electionIntervalRef.current) {
|
|
189750
189945
|
clearInterval(electionIntervalRef.current);
|
|
189751
189946
|
electionIntervalRef.current = null;
|
|
189752
189947
|
}
|
|
189753
|
-
|
|
189754
|
-
|
|
189755
|
-
|
|
189756
|
-
|
|
189757
|
-
|
|
189758
|
-
|
|
189759
|
-
|
|
189760
|
-
|
|
189761
|
-
|
|
189762
|
-
|
|
189763
|
-
|
|
189764
|
-
|
|
189765
|
-
|
|
189766
|
-
|
|
189767
|
-
|
|
189768
|
-
|
|
189769
|
-
|
|
189770
|
-
|
|
189771
|
-
|
|
189772
|
-
|
|
189773
|
-
|
|
189948
|
+
return;
|
|
189949
|
+
}
|
|
189950
|
+
const owner = await proxyRegistry.getProxyOwner();
|
|
189951
|
+
if (!owner || !isProcessRunning(owner.pid)) {
|
|
189952
|
+
writeLog("system", "Proxy owner died, attempting election");
|
|
189953
|
+
const won = await proxyRegistry.attemptElection(instanceKey);
|
|
189954
|
+
if (won) {
|
|
189955
|
+
writeLog("system", "Won election, starting HTTP proxy");
|
|
189956
|
+
if (electionIntervalRef.current) {
|
|
189957
|
+
clearInterval(electionIntervalRef.current);
|
|
189958
|
+
electionIntervalRef.current = null;
|
|
189959
|
+
}
|
|
189960
|
+
try {
|
|
189961
|
+
const electionServices = await proxyRegistry.getAllServices();
|
|
189962
|
+
const electionKeyRegistrations = await proxyRegistry.getAllKeyRegistrations();
|
|
189963
|
+
const electionKeyNames = Object.keys(electionKeyRegistrations);
|
|
189964
|
+
const certificate = generateCertificate("local.spcf.app", electionKeyNames);
|
|
189965
|
+
const proxy2 = await startHttpProxy(
|
|
189966
|
+
exposedServices,
|
|
189967
|
+
certificate,
|
|
189968
|
+
getState,
|
|
189969
|
+
instanceKey
|
|
189970
|
+
);
|
|
189971
|
+
startedProxy = proxy2;
|
|
189972
|
+
proxyRef.current = proxy2;
|
|
189973
|
+
setState((s) => ({ ...s, proxy: proxy2, isProxyOwner: true }));
|
|
189974
|
+
registryWatcherCleanupRef.current = proxyRegistry.watchRegistry((updatedServices, updatedKeys) => {
|
|
189975
|
+
writeLog("system", `Registry updated: ${updatedServices.length} services`);
|
|
189976
|
+
proxy2.updateServices(updatedServices, updatedKeys);
|
|
189977
|
+
});
|
|
189978
|
+
proxy2.updateServices(electionServices, electionKeyRegistrations);
|
|
189979
|
+
} catch (err) {
|
|
189980
|
+
writeLog("system:error", `Failed to start proxy after election: ${err}`);
|
|
189981
|
+
}
|
|
189774
189982
|
}
|
|
189775
189983
|
}
|
|
189776
|
-
}
|
|
189777
|
-
}
|
|
189984
|
+
}, 1e3);
|
|
189985
|
+
}
|
|
189778
189986
|
}
|
|
189779
189987
|
if (cancelled) return;
|
|
189780
189988
|
writeLog("system", "Dev server running");
|
|
@@ -189920,39 +190128,58 @@ Add them to the config block in specific.local`);
|
|
|
189920
190128
|
const staticItems = [
|
|
189921
190129
|
{
|
|
189922
190130
|
key: "title",
|
|
189923
|
-
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Specific dev server"), instanceKey !== "default" && /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " [", instanceKey, "]"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (Ctrl+C to stop)"))
|
|
190131
|
+
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Specific dev server"), tunnelEnabled && /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, " [tunnel]"), instanceKey !== "default" && /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " [", instanceKey, "]"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (Ctrl+C to stop)"))
|
|
189924
190132
|
},
|
|
189925
190133
|
{ key: "space1", content: /* @__PURE__ */ React6.createElement(Text6, null, " ") },
|
|
189926
|
-
// Show admin UI URL
|
|
189927
|
-
|
|
189928
|
-
|
|
189929
|
-
|
|
189930
|
-
|
|
189931
|
-
|
|
189932
|
-
|
|
189933
|
-
|
|
189934
|
-
|
|
189935
|
-
|
|
189936
|
-
|
|
189937
|
-
|
|
189938
|
-
|
|
189939
|
-
|
|
189940
|
-
|
|
189941
|
-
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, " \u25CF "), /* @__PURE__ */ React6.createElement(Text6, null, svc.name), defaultPort && /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (localhost:", defaultPort, ")"))
|
|
189942
|
-
}];
|
|
189943
|
-
}
|
|
189944
|
-
return endpoints.map((endpoint) => {
|
|
189945
|
-
const port = svc.ports.get(endpoint.name);
|
|
189946
|
-
const displayName = endpoint.name === "default" ? svc.name : `${svc.name}:${endpoint.name}`;
|
|
189947
|
-
const proxyName = endpoint.name === "default" ? svc.name : `${svc.name}-${endpoint.name}`;
|
|
190134
|
+
// Show admin UI URL (only in non-tunnel mode)
|
|
190135
|
+
...!tunnelEnabled ? [
|
|
190136
|
+
{
|
|
190137
|
+
key: "admin",
|
|
190138
|
+
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Admin:"), /* @__PURE__ */ React6.createElement(Text6, null, " "), /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "https://", instanceKey === "default" ? "" : `${instanceKey}.`, "local.spcf.app"))
|
|
190139
|
+
},
|
|
190140
|
+
{ key: "admin-space", content: /* @__PURE__ */ React6.createElement(Text6, null, " ") }
|
|
190141
|
+
] : [],
|
|
190142
|
+
// Services section - different rendering for tunnel mode vs local mode
|
|
190143
|
+
...tunnelEnabled ? (
|
|
190144
|
+
// Tunnel mode: show tunnel URLs for public services
|
|
190145
|
+
state.tunnels.size > 0 || state.tunnelStatus.size > 0 ? [
|
|
190146
|
+
{ key: "svc-header", content: /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Services:") },
|
|
190147
|
+
...[...state.tunnelStatus.entries()].map(([serviceName, status]) => {
|
|
190148
|
+
const tunnel = state.tunnels.get(serviceName);
|
|
189948
190149
|
return {
|
|
189949
|
-
key: `svc-${
|
|
189950
|
-
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, " \u25CF "), /* @__PURE__ */ React6.createElement(Text6, null,
|
|
190150
|
+
key: `svc-${serviceName}`,
|
|
190151
|
+
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { color: status === "connected" ? "green" : status === "error" ? "red" : "yellow" }, " \u25CF "), /* @__PURE__ */ React6.createElement(Text6, null, serviceName), status === "connected" && tunnel ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, null, " \u2192 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true }, tunnel.url)) : status === "connecting" ? /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " connecting...") : /* @__PURE__ */ React6.createElement(Text6, { color: "red" }, " error"))
|
|
189951
190152
|
};
|
|
189952
|
-
})
|
|
189953
|
-
|
|
189954
|
-
|
|
189955
|
-
|
|
190153
|
+
}),
|
|
190154
|
+
{ key: "space2", content: /* @__PURE__ */ React6.createElement(Text6, null, " ") }
|
|
190155
|
+
] : []
|
|
190156
|
+
) : (
|
|
190157
|
+
// Local mode: show local URLs
|
|
190158
|
+
services.length > 0 ? [
|
|
190159
|
+
{ key: "svc-header", content: /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Services:") },
|
|
190160
|
+
...services.flatMap((svc) => {
|
|
190161
|
+
const serviceConfig = config.services.find((s) => s.name === svc.name);
|
|
190162
|
+
const endpoints = serviceConfig?.endpoints || [];
|
|
190163
|
+
if (endpoints.length === 0 && svc.ports.size > 0) {
|
|
190164
|
+
const defaultPort = svc.ports.get("default");
|
|
190165
|
+
return [{
|
|
190166
|
+
key: `svc-${svc.name}`,
|
|
190167
|
+
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, " \u25CF "), /* @__PURE__ */ React6.createElement(Text6, null, svc.name), defaultPort && /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (localhost:", defaultPort, ")"))
|
|
190168
|
+
}];
|
|
190169
|
+
}
|
|
190170
|
+
return endpoints.map((endpoint) => {
|
|
190171
|
+
const port = svc.ports.get(endpoint.name);
|
|
190172
|
+
const displayName = endpoint.name === "default" ? svc.name : `${svc.name}:${endpoint.name}`;
|
|
190173
|
+
const proxyName = endpoint.name === "default" ? svc.name : `${svc.name}-${endpoint.name}`;
|
|
190174
|
+
return {
|
|
190175
|
+
key: `svc-${svc.name}-${endpoint.name}`,
|
|
190176
|
+
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, " \u25CF "), /* @__PURE__ */ React6.createElement(Text6, null, displayName), port ? endpoint.public ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, null, " \u2192 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "https://", proxyName, instanceKey === "default" ? "" : `.${instanceKey}`, ".local.spcf.app"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (localhost:", port, ")")) : /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (localhost:", port, ")") : null)
|
|
190177
|
+
};
|
|
190178
|
+
});
|
|
190179
|
+
}),
|
|
190180
|
+
{ key: "space2", content: /* @__PURE__ */ React6.createElement(Text6, null, " ") }
|
|
190181
|
+
] : []
|
|
190182
|
+
),
|
|
189956
190183
|
...config.postgres.length > 0 ? [
|
|
189957
190184
|
{ key: "pg-header", content: /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Postgres:") },
|
|
189958
190185
|
...config.postgres.map((pg) => {
|
|
@@ -190000,13 +190227,13 @@ Add them to the config block in specific.local`);
|
|
|
190000
190227
|
];
|
|
190001
190228
|
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Static, { items: staticItems }, (item) => /* @__PURE__ */ React6.createElement(Box6, { key: item.key }, item.content)));
|
|
190002
190229
|
}
|
|
190003
|
-
function devCommand(instanceKey) {
|
|
190004
|
-
render4(/* @__PURE__ */ React6.createElement(DevUI, { instanceKey }));
|
|
190230
|
+
function devCommand(instanceKey, tunnelEnabled = false) {
|
|
190231
|
+
render4(/* @__PURE__ */ React6.createElement(DevUI, { instanceKey, tunnelEnabled }));
|
|
190005
190232
|
}
|
|
190006
190233
|
|
|
190007
190234
|
// src/lib/dev/git-worktree.ts
|
|
190008
190235
|
import { execSync as execSync2 } from "child_process";
|
|
190009
|
-
import * as
|
|
190236
|
+
import * as path18 from "path";
|
|
190010
190237
|
function isInWorktree() {
|
|
190011
190238
|
try {
|
|
190012
190239
|
const commonDir = execSync2("git rev-parse --git-common-dir", {
|
|
@@ -190017,8 +190244,8 @@ function isInWorktree() {
|
|
|
190017
190244
|
encoding: "utf-8",
|
|
190018
190245
|
stdio: ["pipe", "pipe", "pipe"]
|
|
190019
190246
|
}).trim();
|
|
190020
|
-
const resolvedCommonDir =
|
|
190021
|
-
const resolvedGitDir =
|
|
190247
|
+
const resolvedCommonDir = path18.resolve(commonDir);
|
|
190248
|
+
const resolvedGitDir = path18.resolve(gitDir);
|
|
190022
190249
|
return resolvedCommonDir !== resolvedGitDir;
|
|
190023
190250
|
} catch {
|
|
190024
190251
|
return false;
|
|
@@ -190033,7 +190260,7 @@ function getWorktreeName() {
|
|
|
190033
190260
|
encoding: "utf-8",
|
|
190034
190261
|
stdio: ["pipe", "pipe", "pipe"]
|
|
190035
190262
|
}).trim();
|
|
190036
|
-
return
|
|
190263
|
+
return path18.basename(gitDir);
|
|
190037
190264
|
} catch {
|
|
190038
190265
|
return null;
|
|
190039
190266
|
}
|
|
@@ -190048,35 +190275,35 @@ init_open();
|
|
|
190048
190275
|
import React7, { useState as useState6, useEffect as useEffect4, useCallback } from "react";
|
|
190049
190276
|
import { render as render5, Text as Text7, Box as Box7, useApp as useApp3, useInput as useInput5 } from "ink";
|
|
190050
190277
|
import Spinner5 from "ink-spinner";
|
|
190051
|
-
import * as
|
|
190052
|
-
import * as
|
|
190278
|
+
import * as fs22 from "fs";
|
|
190279
|
+
import * as path20 from "path";
|
|
190053
190280
|
|
|
190054
190281
|
// src/lib/deploy/build-tester.ts
|
|
190055
190282
|
import { spawn as spawn5 } from "child_process";
|
|
190056
|
-
import { existsSync as
|
|
190057
|
-
import { join as
|
|
190283
|
+
import { existsSync as existsSync18 } from "fs";
|
|
190284
|
+
import { join as join19 } from "path";
|
|
190058
190285
|
function getDependencyInstallCommand(build, projectDir) {
|
|
190059
190286
|
switch (build.base) {
|
|
190060
190287
|
case "node":
|
|
190061
|
-
if (
|
|
190288
|
+
if (existsSync18(join19(projectDir, "pnpm-lock.yaml"))) {
|
|
190062
190289
|
return "pnpm install --frozen-lockfile";
|
|
190063
|
-
} else if (
|
|
190290
|
+
} else if (existsSync18(join19(projectDir, "yarn.lock"))) {
|
|
190064
190291
|
return "yarn install --frozen-lockfile";
|
|
190065
|
-
} else if (
|
|
190292
|
+
} else if (existsSync18(join19(projectDir, "package-lock.json"))) {
|
|
190066
190293
|
return "npm ci";
|
|
190067
190294
|
} else {
|
|
190068
190295
|
return "npm install";
|
|
190069
190296
|
}
|
|
190070
190297
|
case "python":
|
|
190071
|
-
if (
|
|
190298
|
+
if (existsSync18(join19(projectDir, "poetry.lock"))) {
|
|
190072
190299
|
return "poetry install --no-interaction";
|
|
190073
|
-
} else if (
|
|
190300
|
+
} else if (existsSync18(join19(projectDir, "Pipfile.lock"))) {
|
|
190074
190301
|
return "pipenv install --deploy";
|
|
190075
|
-
} else if (
|
|
190302
|
+
} else if (existsSync18(join19(projectDir, "Pipfile"))) {
|
|
190076
190303
|
return "pipenv install";
|
|
190077
|
-
} else if (
|
|
190304
|
+
} else if (existsSync18(join19(projectDir, "pyproject.toml"))) {
|
|
190078
190305
|
return "pip install .";
|
|
190079
|
-
} else if (
|
|
190306
|
+
} else if (existsSync18(join19(projectDir, "requirements.txt"))) {
|
|
190080
190307
|
return "pip install -r requirements.txt";
|
|
190081
190308
|
}
|
|
190082
190309
|
return null;
|
|
@@ -190222,8 +190449,8 @@ async function testAllBuilds(builds, projectDir) {
|
|
|
190222
190449
|
|
|
190223
190450
|
// src/lib/tarball/create.ts
|
|
190224
190451
|
import { execSync as execSync3 } from "child_process";
|
|
190225
|
-
import * as
|
|
190226
|
-
import * as
|
|
190452
|
+
import * as fs21 from "fs";
|
|
190453
|
+
import * as path19 from "path";
|
|
190227
190454
|
import { createTarPacker, createEntryItemGenerator } from "tar-vern";
|
|
190228
190455
|
function isInsideGitRepository(dir) {
|
|
190229
190456
|
try {
|
|
@@ -190280,10 +190507,10 @@ var EXCLUDED_DIRS = [
|
|
|
190280
190507
|
];
|
|
190281
190508
|
async function collectPaths(baseDir, currentDir, exclude) {
|
|
190282
190509
|
const results = [];
|
|
190283
|
-
const entries = await
|
|
190510
|
+
const entries = await fs21.promises.readdir(currentDir, { withFileTypes: true });
|
|
190284
190511
|
for (const entry of entries) {
|
|
190285
|
-
const fullPath =
|
|
190286
|
-
const relativePath =
|
|
190512
|
+
const fullPath = path19.join(currentDir, entry.name);
|
|
190513
|
+
const relativePath = path19.relative(baseDir, fullPath);
|
|
190287
190514
|
if (entry.isDirectory()) {
|
|
190288
190515
|
if (!exclude.includes(entry.name)) {
|
|
190289
190516
|
results.push(relativePath);
|
|
@@ -190298,8 +190525,8 @@ async function collectPaths(baseDir, currentDir, exclude) {
|
|
|
190298
190525
|
}
|
|
190299
190526
|
async function createTarArchive(projectDir) {
|
|
190300
190527
|
writeLog("tarball", "Creating tarball using tar-vern (non-git project)");
|
|
190301
|
-
const configPath =
|
|
190302
|
-
if (!
|
|
190528
|
+
const configPath = path19.join(projectDir, "specific.hcl");
|
|
190529
|
+
if (!fs21.existsSync(configPath)) {
|
|
190303
190530
|
throw new Error("specific.hcl not found in project directory");
|
|
190304
190531
|
}
|
|
190305
190532
|
const relativePaths = await collectPaths(projectDir, projectDir, EXCLUDED_DIRS);
|
|
@@ -190316,8 +190543,8 @@ async function createTarArchive(projectDir) {
|
|
|
190316
190543
|
}
|
|
190317
190544
|
function findWidestContext(projectDir, contexts) {
|
|
190318
190545
|
if (contexts.length === 0) return ".";
|
|
190319
|
-
const absolute = contexts.map((c) =>
|
|
190320
|
-
const segments = absolute.map((p) => p.split(
|
|
190546
|
+
const absolute = contexts.map((c) => path19.resolve(projectDir, c));
|
|
190547
|
+
const segments = absolute.map((p) => p.split(path19.sep).filter(Boolean));
|
|
190321
190548
|
const firstSegments = segments[0];
|
|
190322
190549
|
if (!firstSegments) return ".";
|
|
190323
190550
|
const minLen = Math.min(...segments.map((s) => s.length));
|
|
@@ -190331,12 +190558,12 @@ function findWidestContext(projectDir, contexts) {
|
|
|
190331
190558
|
}
|
|
190332
190559
|
}
|
|
190333
190560
|
const ancestorSegments = firstSegments.slice(0, commonLength);
|
|
190334
|
-
const ancestor =
|
|
190335
|
-
return
|
|
190561
|
+
const ancestor = path19.sep + ancestorSegments.join(path19.sep);
|
|
190562
|
+
return path19.relative(projectDir, ancestor) || ".";
|
|
190336
190563
|
}
|
|
190337
190564
|
async function createProjectTarball(projectDir, context = ".") {
|
|
190338
|
-
const contextDir =
|
|
190339
|
-
const appPath =
|
|
190565
|
+
const contextDir = path19.resolve(projectDir, context);
|
|
190566
|
+
const appPath = path19.relative(contextDir, projectDir) || ".";
|
|
190340
190567
|
writeLog("tarball", `Context: ${contextDir}, appPath: ${appPath}`);
|
|
190341
190568
|
let tarball;
|
|
190342
190569
|
if (isInsideGitRepository(contextDir)) {
|
|
@@ -191223,14 +191450,14 @@ ${errorMsg}`
|
|
|
191223
191450
|
), phase === "error" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "red" }, "Error: ", error)), phase === "success" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { color: "green" }, "Deployment successful!"), deployment?.publicUrls && Object.keys(deployment.publicUrls).length > 0 && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Public URLs:"), Object.entries(deployment.publicUrls).map(([name, url]) => /* @__PURE__ */ React7.createElement(Text7, { key: name }, " ", name, ": ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, url))))));
|
|
191224
191451
|
}
|
|
191225
191452
|
async function deployCommand(environment, options2) {
|
|
191226
|
-
const configPath =
|
|
191227
|
-
if (!
|
|
191453
|
+
const configPath = path20.join(process.cwd(), "specific.hcl");
|
|
191454
|
+
if (!fs22.existsSync(configPath)) {
|
|
191228
191455
|
console.error("Error: No specific.hcl found in current directory");
|
|
191229
191456
|
process.exit(1);
|
|
191230
191457
|
}
|
|
191231
191458
|
let config;
|
|
191232
191459
|
try {
|
|
191233
|
-
const hcl =
|
|
191460
|
+
const hcl = fs22.readFileSync(configPath, "utf-8");
|
|
191234
191461
|
config = await parseConfig(hcl);
|
|
191235
191462
|
} catch (err) {
|
|
191236
191463
|
console.error(
|
|
@@ -191254,8 +191481,8 @@ async function deployCommand(environment, options2) {
|
|
|
191254
191481
|
|
|
191255
191482
|
// src/commands/exec.tsx
|
|
191256
191483
|
import { spawn as spawn6 } from "child_process";
|
|
191257
|
-
import * as
|
|
191258
|
-
import * as
|
|
191484
|
+
import * as fs23 from "fs";
|
|
191485
|
+
import * as path21 from "path";
|
|
191259
191486
|
async function execCommand(serviceName, command, instanceKey = "default") {
|
|
191260
191487
|
if (command.length === 0) {
|
|
191261
191488
|
console.error(
|
|
@@ -191283,14 +191510,14 @@ async function execCommand(serviceName, command, instanceKey = "default") {
|
|
|
191283
191510
|
}
|
|
191284
191511
|
}
|
|
191285
191512
|
};
|
|
191286
|
-
const configPath =
|
|
191287
|
-
if (!
|
|
191513
|
+
const configPath = path21.join(process.cwd(), "specific.hcl");
|
|
191514
|
+
if (!fs23.existsSync(configPath)) {
|
|
191288
191515
|
console.error("Error: No specific.hcl found in current directory");
|
|
191289
191516
|
process.exit(1);
|
|
191290
191517
|
}
|
|
191291
191518
|
let config;
|
|
191292
191519
|
try {
|
|
191293
|
-
const hcl =
|
|
191520
|
+
const hcl = fs23.readFileSync(configPath, "utf-8");
|
|
191294
191521
|
config = await parseConfig(hcl);
|
|
191295
191522
|
} catch (err) {
|
|
191296
191523
|
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -191484,21 +191711,21 @@ async function psqlCommand(databaseName, instanceKey = "default") {
|
|
|
191484
191711
|
import React8, { useState as useState7, useEffect as useEffect5 } from "react";
|
|
191485
191712
|
import { render as render6, Text as Text8, Box as Box8 } from "ink";
|
|
191486
191713
|
import Spinner6 from "ink-spinner";
|
|
191487
|
-
import * as
|
|
191488
|
-
import * as
|
|
191714
|
+
import * as fs24 from "fs";
|
|
191715
|
+
import * as path22 from "path";
|
|
191489
191716
|
function CleanUI({ instanceKey }) {
|
|
191490
191717
|
const [state, setState] = useState7({ status: "checking" });
|
|
191491
191718
|
useEffect5(() => {
|
|
191492
191719
|
async function clean() {
|
|
191493
191720
|
const projectRoot = process.cwd();
|
|
191494
|
-
const specificDir =
|
|
191495
|
-
if (!
|
|
191721
|
+
const specificDir = path22.join(projectRoot, ".specific");
|
|
191722
|
+
if (!fs24.existsSync(specificDir)) {
|
|
191496
191723
|
setState({ status: "nothing" });
|
|
191497
191724
|
return;
|
|
191498
191725
|
}
|
|
191499
191726
|
if (instanceKey) {
|
|
191500
|
-
const keyDir =
|
|
191501
|
-
if (!
|
|
191727
|
+
const keyDir = path22.join(specificDir, "keys", instanceKey);
|
|
191728
|
+
if (!fs24.existsSync(keyDir)) {
|
|
191502
191729
|
setState({ status: "nothing" });
|
|
191503
191730
|
return;
|
|
191504
191731
|
}
|
|
@@ -191514,7 +191741,7 @@ function CleanUI({ instanceKey }) {
|
|
|
191514
191741
|
await stateManager.cleanStaleState();
|
|
191515
191742
|
setState({ status: "cleaning" });
|
|
191516
191743
|
try {
|
|
191517
|
-
|
|
191744
|
+
fs24.rmSync(keyDir, { recursive: true, force: true });
|
|
191518
191745
|
setState({ status: "success" });
|
|
191519
191746
|
} catch (err) {
|
|
191520
191747
|
setState({
|
|
@@ -191523,10 +191750,10 @@ function CleanUI({ instanceKey }) {
|
|
|
191523
191750
|
});
|
|
191524
191751
|
}
|
|
191525
191752
|
} else {
|
|
191526
|
-
const keysDir =
|
|
191527
|
-
if (
|
|
191528
|
-
const keys =
|
|
191529
|
-
(f) =>
|
|
191753
|
+
const keysDir = path22.join(specificDir, "keys");
|
|
191754
|
+
if (fs24.existsSync(keysDir)) {
|
|
191755
|
+
const keys = fs24.readdirSync(keysDir).filter(
|
|
191756
|
+
(f) => fs24.statSync(path22.join(keysDir, f)).isDirectory()
|
|
191530
191757
|
);
|
|
191531
191758
|
for (const key of keys) {
|
|
191532
191759
|
const stateManager2 = new InstanceStateManager(projectRoot, key);
|
|
@@ -191551,7 +191778,7 @@ function CleanUI({ instanceKey }) {
|
|
|
191551
191778
|
}
|
|
191552
191779
|
setState({ status: "cleaning" });
|
|
191553
191780
|
try {
|
|
191554
|
-
|
|
191781
|
+
fs24.rmSync(specificDir, { recursive: true, force: true });
|
|
191555
191782
|
setState({ status: "success" });
|
|
191556
191783
|
} catch (err) {
|
|
191557
191784
|
setState({
|
|
@@ -191638,13 +191865,13 @@ function logoutCommand() {
|
|
|
191638
191865
|
var program = new Command();
|
|
191639
191866
|
var env = "production";
|
|
191640
191867
|
var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
|
|
191641
|
-
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.
|
|
191868
|
+
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.48").enablePositionalOptions();
|
|
191642
191869
|
program.command("init").description("Initialize project for use with a coding agent").action(initCommand);
|
|
191643
191870
|
program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
|
|
191644
191871
|
program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
|
|
191645
|
-
program.command("dev").description("Start local development environment").option("-k, --key <key>", "Namespace for isolated dev environment (auto-detected from git worktree if not specified)").action((options2) => {
|
|
191872
|
+
program.command("dev").description("Start local development environment").option("-k, --key <key>", "Namespace for isolated dev environment (auto-detected from git worktree if not specified)").option("--tunnel", "Expose public services via localtunnel URLs").action((options2) => {
|
|
191646
191873
|
const key = options2.key ?? getDefaultKey();
|
|
191647
|
-
devCommand(key);
|
|
191874
|
+
devCommand(key, options2.tunnel ?? false);
|
|
191648
191875
|
});
|
|
191649
191876
|
program.command("deploy [environment]").description("Deploy to Specific infrastructure").option("--skip-build-test", "Skip local build testing before deploy").action((environment, options2) => {
|
|
191650
191877
|
deployCommand(environment, options2);
|