@denvig/sdk 0.7.0-alpha.4
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/LICENSE +8 -0
- package/README.md +17 -0
- package/dist/chunk-CMqjfN_6.cjs +1 -0
- package/dist/fs.cjs +1 -0
- package/dist/fs.d.ts +15 -0
- package/dist/fs.js +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +273 -0
- package/dist/index.js +1 -0
- package/dist/internal.cjs +1 -0
- package/dist/internal.d.ts +202 -0
- package/dist/internal.js +1 -0
- package/dist/path-DElA4WIM.js +1 -0
- package/dist/path-pt0IDc1N.cjs +1 -0
- package/dist/project-BA4nj7bB.js +1 -0
- package/dist/project-BqMRVXi0.cjs +1 -0
- package/dist/project-DmYu4L1C.d.ts +247 -0
- package/dist/reconcile-C90QGHEJ.d.ts +50 -0
- package/dist/reconcile-CvY7dT3H.js +1 -0
- package/dist/reconcile-DU2Wb6SO.cjs +1 -0
- package/dist/safeReadFile-BlLUgHuA.cjs +1 -0
- package/dist/safeReadFile-Z2PHqO9j.js +1 -0
- package/dist/semver-CxqsdmU_.cjs +1 -0
- package/dist/semver-dZSpezIR.js +1 -0
- package/dist/teardown-BuHyVdhH.d.ts +764 -0
- package/dist/teardown-DgjT-aE7.js +208 -0
- package/dist/teardown-DypXa0Wy.cjs +208 -0
- package/dist/testing.cjs +1 -0
- package/dist/testing.d.ts +32 -0
- package/dist/testing.js +1 -0
- package/dist/utils.cjs +1 -0
- package/dist/utils.d.ts +15 -0
- package/dist/utils.js +1 -0
- package/package.json +109 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
Copyright © 2026 Marc Qualie, https://marcqualie.com
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
5
|
+
|
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @denvig/sdk
|
|
2
|
+
|
|
3
|
+
The in-process logic layer behind the [denvig](https://github.com/marcqualie/denvig) CLI.
|
|
4
|
+
|
|
5
|
+
`@denvig/sdk` runs denvig's logic directly rather than shelling out to the CLI
|
|
6
|
+
binary, so integrations can take actions and read data programmatically.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { DenvigSDK } from '@denvig/sdk'
|
|
10
|
+
|
|
11
|
+
const denvig = new DenvigSDK({ client: 'my-app' })
|
|
12
|
+
|
|
13
|
+
const services = await denvig.services.list()
|
|
14
|
+
await denvig.services.start('api')
|
|
15
|
+
const outdated = await denvig.deps.outdated()
|
|
16
|
+
const projects = await denvig.projects.list()
|
|
17
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return s}});
|
package/dist/fs.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./safeReadFile-BlLUgHuA.cjs");exports.isDirectory=e.t,exports.pathExists=e.n,exports.safeReadTextFile=e.r;
|
package/dist/fs.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/lib/safeReadFile.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Returns the contents of a file asynchronously if it exists, or null if it does not.
|
|
4
|
+
*/
|
|
5
|
+
declare const safeReadTextFile: (path: string) => Promise<string | null>;
|
|
6
|
+
/**
|
|
7
|
+
* Check if a path exists on the filesystem.
|
|
8
|
+
*/
|
|
9
|
+
declare const pathExists: (path: string) => Promise<boolean>;
|
|
10
|
+
/**
|
|
11
|
+
* Check if a path is a directory.
|
|
12
|
+
*/
|
|
13
|
+
declare const isDirectory: (path: string) => Promise<boolean>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { isDirectory, pathExists, safeReadTextFile };
|
package/dist/fs.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as e,r as t,t as n}from"./safeReadFile-Z2PHqO9j.js";export{n as isDirectory,e as pathExists,t as safeReadTextFile};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./project-BqMRVXi0.cjs"),t=require("./teardown-DypXa0Wy.cjs"),n=require("./safeReadFile-BlLUgHuA.cjs"),r=require("./reconcile-DU2Wb6SO.cjs");let i=require("node:child_process"),a=require("node:path"),o=require("node:crypto"),s=require("node:fs");const c=async({cwd:n,project:r})=>{let i=null;if(r)i=(await e.i(r,t.T)).path;else{let e=t.v(n);if(e)i=e;else{let e=(await t.m()).find(e=>n===e.path||n.startsWith(`${e.path}/`));i=e?e.path:n}}let a=i?await t.g.retrieve(i):null,o=i?await t.y(i):null;return{project:a,projectPath:i,slug:o}},l=async(e={})=>{let n=t.z(),r;try{r=(0,s.readdirSync)(n).filter(e=>{try{return(0,s.statSync)((0,a.resolve)(n,e)).isDirectory()}catch{return!1}})}catch{r=[]}if(r.length===0)return[];let i=await t.V(),o=i?(0,s.readFileSync)(t.F(),`utf-8`):null,c=i?await t.H():!1,l=new Date,u=[];for(let e of r){let r=t.M((0,a.resolve)(n,e));if(r)try{let n=(0,s.readFileSync)(r,`utf-8`),i=t.K(n),a=t.L(n),d=o?t.U(n,o):t.W(n),f=t.R(n),p;p=a<=l?`expired`:d&&!c?`untrusted`:`valid`,u.push({name:e,domains:i.length>0?i:[e],issuer:f,expires:a.toISOString(),status:p,signedByLocalCa:d,caTrusted:c})}catch{}}if(e.domain){let t=e.domain;return u.filter(e=>e.domains.includes(t))}return u},u=n=>{if(n.name)return(0,a.resolve)(t.z(),n.name);if(n.domain)return t.I(n.domain);throw new e.d("A certificate `domain` or `name` is required.")},d=async e=>{let t=u(e);return!(0,s.existsSync)(t)||!(0,s.statSync)(t).isDirectory()?null:{name:(0,a.basename)(t),path:t,files:(0,s.readdirSync)(t)}},f=async n=>{let{domain:r,force:i}=n;if(!await t.V())throw new e.d("The local CA is not configured. Run `ca.configure()` first.");let o=t.I(r);if((0,s.existsSync)(o)&&!i)throw new e.d(`A certificate already exists for "${r}". Pass force to overwrite.`);let{cert:c,key:l}=await t.G(),{privkey:u,fullchain:d}=await t.P(r,c,l);return await t.Y(r,u,d),{domain:r,name:(0,a.basename)(o),privkey:(0,a.resolve)(o,`privkey.pem`),fullchain:(0,a.resolve)(o,`fullchain.pem`)}},p=async t=>{let n=await d(t);if(!n)throw new e.d(`Certificate "${t.name??t.domain}" not found.`);return(0,s.rmSync)(n.path,{recursive:!0}),{name:n.name,files:n.files}},m=async n=>{let r=(0,a.resolve)(n.keyPath),i=(0,a.resolve)(n.certPath),o;try{o=(0,s.readFileSync)(i,`utf-8`)}catch{throw new e.d(`Could not read certificate file: ${i}`)}let c=t.K(o);if(c.length===0)throw new e.d(`Could not determine a domain from the certificate.`);let l=n.name?.replace(/^\*\./,`_wildcard.`),u=l??c[0],d=l?(0,a.resolve)(t.z(),l):t.I(u);(0,s.mkdirSync)(d,{recursive:!0});let f=(0,a.resolve)(d,`privkey.pem`),p=(0,a.resolve)(d,`fullchain.pem`);return(0,s.copyFileSync)(r,f),(0,s.chmodSync)(f,384),(0,s.copyFileSync)(i,p),{domain:u,name:(0,a.basename)(d),privkey:f,fullchain:p}},h=async()=>{let e=t.F();if(!await t.V())return{initialized:!1,trusted:!1,path:e};let n=await t.H(),r=new o.X509Certificate((0,s.readFileSync)(e,`utf-8`));return{initialized:!0,trusted:n,path:e,subject:r.subject,issuer:r.issuer,validFrom:r.validFrom,validTo:r.validTo,serialNumber:r.serialNumber,fingerprint256:r.fingerprint256}},g=async()=>{let e=t.F();if(await t.V())return t.B(e),{created:!1,path:e};let{certPem:n,keyPem:r}=await t.N();return await t.J(n,r),t.B(e),{created:!0,path:e}},_=async()=>{let n=t.F();if(!await t.V())throw new e.d("The local CA is not configured. Run `ca.configure()` first.");return t.q(n),{path:n}},v=()=>{try{let e=(0,i.execSync)(`brew services info nginx --json`,{stdio:[`pipe`,`pipe`,`pipe`],encoding:`utf-8`}),t=JSON.parse(e),n=Array.isArray(t)?t[0]:t;return{running:n.running??!1,pid:n.pid??null,status:n.status??null}}catch{return{running:!1,pid:null,status:null}}},y=async(e={})=>{let r=(await t.E()).experimental?.gateway,i=r?.enabled??!1,a=[],{project:o}=await c({cwd:e.cwd??process.cwd()});if(o){let e=o.activeWorktree,i=e.config.services||{};for(let[o,s]of Object.entries(i)){let i=s.http?.domain;if(!i)continue;let c=s.http?.secure??!1,l=c?await t.O(i):null,u=l?await t.A(l):null,d=r?.enabled?t.o(e.id,o,r.configsPath):null;a.push({name:o,domain:i,cnames:s.http?.cnames||[],port:s.http?.port,secure:c,certFound:!!u,certDir:l,nginxConfigPath:d,nginxConfigExists:d?await n.n(d):!1})}}return{enabled:i,handler:r?.handler??`nginx`,configsPath:r?.configsPath??null,nginxConf:r?.configsPath?t.a(r.configsPath):null,nginx:v(),services:a}},b=async()=>({reconcile:await r.t(),gateway:await t.i()}),x=/^[a-z]([a-z0-9-]*[a-z0-9])?$/;var S=class{ctx;constructor(e){if(!x.test(e.client))throw Error(`Invalid client name "${e.client}". Must start with a letter, contain only lowercase alphanumeric and hyphens, and not end with a hyphen.`);this.ctx={client:e.client,cwd:e.cwd??process.cwd()}}version(){return e.s()}projects={retrieve:async t=>{let n=await c({cwd:this.ctx.cwd,project:t});if(!n.project)throw new e.d(`Project "${t}" could not be resolved.`);return new e.t(n.project,this.ctx)},list:n=>e.o(this.ctx,`projects.list`,null,async()=>{let r=await t.m(n),i=new Map;for(let{path:n}of r){let r=await t.g.retrieve(n),a=r.primaryWorktree.path;i.has(a)||(r.activeWorktree=r.primaryWorktree,i.set(a,new e.t(r,this.ctx)))}return[...i.values()].sort((e,t)=>e.path.localeCompare(t.path))}),detect:async t=>{let n=await c({cwd:this.ctx.cwd,project:t});return{project:n.project?new e.t(n.project,this.ctx):null,projectPath:n.projectPath,slug:n.slug}}};certs={list:t=>e.o(this.ctx,`certs.list`,null,()=>l(t)),retrieve:t=>e.o(this.ctx,`certs.retrieve`,null,()=>d(t)),create:t=>e.o(this.ctx,`certs.create`,null,()=>f(t)),remove:t=>e.o(this.ctx,`certs.remove`,null,()=>p(t)),import:t=>e.o(this.ctx,`certs.import`,null,()=>m(t)),ca:{status:()=>e.o(this.ctx,`certs.ca.status`,null,()=>h()),configure:()=>e.o(this.ctx,`certs.ca.configure`,null,()=>g()),remove:()=>e.o(this.ctx,`certs.ca.remove`,null,()=>_())}};gateway={status:()=>e.o(this.ctx,`gateway.status`,null,()=>y({cwd:this.ctx.cwd})),configure:()=>e.o(this.ctx,`gateway.configure`,null,()=>b())};config={retrieve:async n=>{if(!n?.project)return t.E();let r=await c({cwd:this.ctx.cwd,project:n.project});if(!r.project)throw new e.d(`Project "${n.project}" could not be resolved.`);return r.project.activeWorktree.config}}};exports.DenvigAction=e.a,exports.DenvigError=e.c,exports.DenvigOperationError=e.l,exports.DenvigProject=e.t,exports.DenvigSDK=S,exports.DenvigSDKError=e.u,exports.DenvigService=e.r,exports.DenvigValidationError=e.d,exports.DenvigWorktree=e.n,exports.ProjectConfigSchema=t.D;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { D as ProjectConfigSchema, E as GlobalConfigSchema, _ as ProjectInfo, c as TreeNode, h as ServiceResponse, p as Dependency, v as ServiceStatus, w as ConfigWithSourcePaths } from "./teardown-BuHyVdhH.js";
|
|
2
|
+
import { a as DenvigAction, i as DenvigService, n as ServiceRow, r as DenvigWorktree, t as DenvigProject } from "./project-DmYu4L1C.js";
|
|
3
|
+
import { t as ReconcileResult } from "./reconcile-C90QGHEJ.js";
|
|
4
|
+
|
|
5
|
+
//#region src/lib/errors.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Base error for all denvig SDK failures.
|
|
8
|
+
*/
|
|
9
|
+
declare class DenvigError extends Error {
|
|
10
|
+
constructor(message: string);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Thrown when an operation receives invalid input (bad flags/options).
|
|
14
|
+
* The CLI maps these to a usage error.
|
|
15
|
+
*/
|
|
16
|
+
declare class DenvigValidationError extends DenvigError {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Thrown when an operation fails at runtime (e.g. a service refuses to start).
|
|
21
|
+
*/
|
|
22
|
+
declare class DenvigOperationError extends DenvigError {
|
|
23
|
+
/** Optional machine-readable details (service name, project slug, …). */
|
|
24
|
+
readonly details?: Record<string, unknown>;
|
|
25
|
+
constructor(message: string, details?: Record<string, unknown>);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Public error surface for SDK consumers. The SDK wraps any underlying failure
|
|
29
|
+
* in this type so callers have a single error to catch.
|
|
30
|
+
*
|
|
31
|
+
* The `stderr`/`stdout` fields are retained for backwards compatibility with
|
|
32
|
+
* the previous shell-based SDK and are now generally `undefined`.
|
|
33
|
+
*/
|
|
34
|
+
declare class DenvigSDKError extends Error {
|
|
35
|
+
readonly originalMessage?: string;
|
|
36
|
+
readonly stderr?: string;
|
|
37
|
+
readonly stdout?: string;
|
|
38
|
+
constructor(message: string, originalMessage?: string, stderr?: string, stdout?: string);
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/lib/projects.d.ts
|
|
42
|
+
type ListProjectsOptions = {
|
|
43
|
+
/** Only include projects with a .denvig.yml configuration file */withConfig?: boolean;
|
|
44
|
+
};
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/operations/certs.d.ts
|
|
47
|
+
type CertificateStatus = 'valid' | 'expired' | 'untrusted';
|
|
48
|
+
/** A managed TLS certificate under `~/.denvig/certs`. */
|
|
49
|
+
type DenvigCertificate = {
|
|
50
|
+
/** The certificate directory name. */name: string; /** Domains the certificate is valid for (falls back to the directory name). */
|
|
51
|
+
domains: string[]; /** Issuer common name, when available. */
|
|
52
|
+
issuer: string | null; /** Expiry as an ISO 8601 timestamp. */
|
|
53
|
+
expires: string; /** `expired` when past expiry, `untrusted` for an untrusted local CA cert. */
|
|
54
|
+
status: CertificateStatus; /** Whether the certificate was signed by the local denvig CA. */
|
|
55
|
+
signedByLocalCa: boolean; /** Whether the local CA is currently trusted in the system keychain. */
|
|
56
|
+
caTrusted: boolean;
|
|
57
|
+
};
|
|
58
|
+
type ListCertificatesOptions = {
|
|
59
|
+
/** Only include certificates valid for this domain. */domain?: string;
|
|
60
|
+
};
|
|
61
|
+
/** A managed certificate's location on disk. */
|
|
62
|
+
type CertificateLocation = {
|
|
63
|
+
/** The certificate directory name. */name: string; /** Absolute path of the certificate directory. */
|
|
64
|
+
path: string; /** Files within the certificate directory. */
|
|
65
|
+
files: string[];
|
|
66
|
+
};
|
|
67
|
+
type CertificateRef = {
|
|
68
|
+
/** Resolve by domain (mapped to its certificate directory). */domain?: string; /** Resolve by certificate directory name. */
|
|
69
|
+
name?: string;
|
|
70
|
+
};
|
|
71
|
+
type CreateCertificateOptions = {
|
|
72
|
+
/** Domain to issue a certificate for (e.g. `hello.denvig.me`, `*.denvig.me`). */domain: string; /** Overwrite an existing certificate for the domain. */
|
|
73
|
+
force?: boolean;
|
|
74
|
+
};
|
|
75
|
+
type CreateCertificateResult = {
|
|
76
|
+
domain: string;
|
|
77
|
+
name: string;
|
|
78
|
+
privkey: string;
|
|
79
|
+
fullchain: string;
|
|
80
|
+
};
|
|
81
|
+
type RemoveCertificateResult = {
|
|
82
|
+
name: string;
|
|
83
|
+
files: string[];
|
|
84
|
+
};
|
|
85
|
+
type ImportCertificateOptions = {
|
|
86
|
+
/** Path to the private key PEM file. */keyPath: string; /** Path to the certificate (fullchain) PEM file. */
|
|
87
|
+
certPath: string; /** Override the certificate directory name (defaults to the detected domain). */
|
|
88
|
+
name?: string;
|
|
89
|
+
};
|
|
90
|
+
type ImportCertificateResult = {
|
|
91
|
+
domain: string;
|
|
92
|
+
name: string;
|
|
93
|
+
privkey: string;
|
|
94
|
+
fullchain: string;
|
|
95
|
+
};
|
|
96
|
+
/** Status of the local Certificate Authority. */
|
|
97
|
+
type CaStatus = {
|
|
98
|
+
/** Whether the CA certificate and key exist on disk. */initialized: boolean; /** Whether the CA is trusted in the system keychain. */
|
|
99
|
+
trusted: boolean; /** Absolute path of the CA certificate. */
|
|
100
|
+
path: string;
|
|
101
|
+
subject?: string;
|
|
102
|
+
issuer?: string;
|
|
103
|
+
validFrom?: string;
|
|
104
|
+
validTo?: string;
|
|
105
|
+
serialNumber?: string;
|
|
106
|
+
fingerprint256?: string;
|
|
107
|
+
};
|
|
108
|
+
type ConfigureCaResult = {
|
|
109
|
+
/** Whether a new CA was generated (false when an existing CA was reused). */created: boolean; /** Absolute path of the CA certificate. */
|
|
110
|
+
path: string;
|
|
111
|
+
};
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/lib/gateway/configure.d.ts
|
|
114
|
+
type ConfigureServiceResult = {
|
|
115
|
+
projectSlug: string;
|
|
116
|
+
serviceName: string;
|
|
117
|
+
domain: string;
|
|
118
|
+
cnames: string[];
|
|
119
|
+
port: number;
|
|
120
|
+
certStatus: 'valid' | 'missing' | 'not_configured';
|
|
121
|
+
certDir?: string;
|
|
122
|
+
certMessage?: string;
|
|
123
|
+
configStatus: 'written' | 'error';
|
|
124
|
+
configMessage?: string;
|
|
125
|
+
};
|
|
126
|
+
type ConfigureGatewayResult = {
|
|
127
|
+
success: boolean;
|
|
128
|
+
removed: string[];
|
|
129
|
+
services: ConfigureServiceResult[];
|
|
130
|
+
nginxReload: boolean;
|
|
131
|
+
nginxReloadMessage?: string;
|
|
132
|
+
message?: string;
|
|
133
|
+
};
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/operations/gateway.d.ts
|
|
136
|
+
/** The nginx process state as reported by `brew services`. */
|
|
137
|
+
type NginxProcessStatus = {
|
|
138
|
+
running: boolean;
|
|
139
|
+
pid: number | null;
|
|
140
|
+
status: string | null;
|
|
141
|
+
};
|
|
142
|
+
/** Gateway status for a single service with an `http.domain`. */
|
|
143
|
+
type GatewayServiceStatus = {
|
|
144
|
+
name: string;
|
|
145
|
+
domain: string;
|
|
146
|
+
cnames: string[];
|
|
147
|
+
port: number | undefined;
|
|
148
|
+
secure: boolean; /** Whether usable SSL key/cert files were resolved for the domain. */
|
|
149
|
+
certFound: boolean; /** The certificate directory backing the domain, if any. */
|
|
150
|
+
certDir: string | null; /** The nginx config path for this service, or `null` when gateway is off. */
|
|
151
|
+
nginxConfigPath: string | null;
|
|
152
|
+
nginxConfigExists: boolean;
|
|
153
|
+
};
|
|
154
|
+
type GatewayStatus = {
|
|
155
|
+
/** Whether the experimental gateway is enabled in the global config. */enabled: boolean; /** The gateway handler (currently always `nginx`). */
|
|
156
|
+
handler: string; /** Directory nginx server configs are written to. */
|
|
157
|
+
configsPath: string | null; /** Path of the generated nginx include file. */
|
|
158
|
+
nginxConf: string | null; /** The nginx process state. */
|
|
159
|
+
nginx: NginxProcessStatus; /** Gateway-configured services for the resolved project. */
|
|
160
|
+
services: GatewayServiceStatus[];
|
|
161
|
+
};
|
|
162
|
+
type ConfigureGatewayOutput = {
|
|
163
|
+
reconcile: ReconcileResult; /** The gateway rebuild result, or `null` when the gateway is not enabled. */
|
|
164
|
+
gateway: ConfigureGatewayResult | null;
|
|
165
|
+
};
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/resources/config.d.ts
|
|
168
|
+
/**
|
|
169
|
+
* A resolved denvig configuration with the source files it was loaded from.
|
|
170
|
+
* `denvig.config.retrieve()` returns the global config; `project.config` and
|
|
171
|
+
* `denvig.config.retrieve({ project })` return a project's config.
|
|
172
|
+
*/
|
|
173
|
+
type DenvigConfig = ConfigWithSourcePaths<GlobalConfigSchema> | ConfigWithSourcePaths<ProjectConfigSchema>;
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/sdk.d.ts
|
|
176
|
+
/** A detected project plus its slug, or nulls when none could be resolved. */
|
|
177
|
+
type DetectProjectResult = {
|
|
178
|
+
project: DenvigProject | null;
|
|
179
|
+
projectPath: string | null;
|
|
180
|
+
slug: string | null;
|
|
181
|
+
};
|
|
182
|
+
type DenvigSDKOptions = {
|
|
183
|
+
/**
|
|
184
|
+
* Client identifier for the integration using the SDK (required). Used for
|
|
185
|
+
* usage logging to track which integration is calling the SDK. Logs include
|
|
186
|
+
* `via: 'sdk:${client}'` (e.g. `sdk:raycast`).
|
|
187
|
+
*/
|
|
188
|
+
client: string;
|
|
189
|
+
/**
|
|
190
|
+
* Working directory used to detect the project when no explicit project is
|
|
191
|
+
* given.
|
|
192
|
+
* @default process.cwd()
|
|
193
|
+
*/
|
|
194
|
+
cwd?: string;
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Denvig SDK for in-process, programmatic access to denvig's logic.
|
|
198
|
+
*
|
|
199
|
+
* The SDK is resource-oriented: resolve a project, then chain into its
|
|
200
|
+
* worktrees, actions, services, dependencies and config. Global concerns
|
|
201
|
+
* (certificates, the global config) hang off the SDK directly.
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```ts
|
|
205
|
+
* import { DenvigSDK } from '@denvig/sdk'
|
|
206
|
+
*
|
|
207
|
+
* const denvig = new DenvigSDK({ client: 'my-app' })
|
|
208
|
+
* const project = await denvig.projects.retrieve('local:/path/to/project')
|
|
209
|
+
* const service = await project.services.retrieve('api')
|
|
210
|
+
* await service.start()
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
declare class DenvigSDK {
|
|
214
|
+
private ctx;
|
|
215
|
+
constructor(options: DenvigSDKOptions);
|
|
216
|
+
/** The version denvig was compiled with. */
|
|
217
|
+
version(): string;
|
|
218
|
+
projects: {
|
|
219
|
+
/**
|
|
220
|
+
* Resolve a project by identifier (`id:…`, `local:…`, `github:…`) or path.
|
|
221
|
+
*/
|
|
222
|
+
retrieve: (identifier: string) => Promise<DenvigProject>;
|
|
223
|
+
/**
|
|
224
|
+
* List every project discovered under the configured project paths. Each
|
|
225
|
+
* project family (its primary checkout plus sibling worktrees) appears once,
|
|
226
|
+
* rooted at the primary.
|
|
227
|
+
*/
|
|
228
|
+
list: (options?: ListProjectsOptions) => Promise<DenvigProject[]>;
|
|
229
|
+
/**
|
|
230
|
+
* Detect the active project from the SDK's `cwd` or an explicit identifier,
|
|
231
|
+
* returning `null` (rather than throwing) when none can be resolved. Used by
|
|
232
|
+
* hosts that must keep running without a project (e.g. the CLI entry point).
|
|
233
|
+
*/
|
|
234
|
+
detect: (identifier?: string) => Promise<DetectProjectResult>;
|
|
235
|
+
};
|
|
236
|
+
certs: {
|
|
237
|
+
/** List managed TLS certificates, optionally filtered by domain. */list: (options?: ListCertificatesOptions) => Promise<DenvigCertificate[]>; /** Look up a managed certificate by `domain` or directory `name`. */
|
|
238
|
+
retrieve: (ref: CertificateRef) => Promise<CertificateLocation | null>; /** Issue a certificate for a domain, signed by the local CA. */
|
|
239
|
+
create: (options: CreateCertificateOptions) => Promise<CreateCertificateResult>; /** Remove a managed certificate by `domain` or directory `name`. */
|
|
240
|
+
remove: (ref: CertificateRef) => Promise<RemoveCertificateResult>; /** Import an existing key/certificate pair into the managed certs. */
|
|
241
|
+
import: (options: ImportCertificateOptions) => Promise<ImportCertificateResult>; /** The local Certificate Authority that signs locally-issued certs. */
|
|
242
|
+
ca: {
|
|
243
|
+
/** Report whether the local CA is configured and its details. */status: () => Promise<CaStatus>; /** Generate the local CA if missing and install it to the keychain. */
|
|
244
|
+
configure: () => Promise<ConfigureCaResult>; /** Remove the local CA from the system keychain. */
|
|
245
|
+
remove: () => Promise<{
|
|
246
|
+
path: string;
|
|
247
|
+
}>;
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
gateway: {
|
|
251
|
+
/**
|
|
252
|
+
* Report the gateway's global state plus the gateway-configured services of
|
|
253
|
+
* the project resolved from the SDK's `cwd`.
|
|
254
|
+
*/
|
|
255
|
+
status: () => Promise<GatewayStatus>; /** Reconcile services and rebuild every nginx config from runtime state. */
|
|
256
|
+
configure: () => Promise<ConfigureGatewayOutput>;
|
|
257
|
+
};
|
|
258
|
+
config: {
|
|
259
|
+
/**
|
|
260
|
+
* Retrieve configuration. Without `project`, returns the global config;
|
|
261
|
+
* with `project`, returns that project's configuration.
|
|
262
|
+
*/
|
|
263
|
+
retrieve: (options?: {
|
|
264
|
+
project?: string;
|
|
265
|
+
}) => Promise<DenvigConfig>;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
//#endregion
|
|
269
|
+
//#region src/resources/dependency.d.ts
|
|
270
|
+
/** A project dependency surfaced by `project.dependencies`. */
|
|
271
|
+
type DenvigDependency = Dependency;
|
|
272
|
+
//#endregion
|
|
273
|
+
export { type CaStatus, DenvigAction, type DenvigCertificate, type DenvigConfig, type DenvigDependency, DenvigError, DenvigOperationError, DenvigProject, DenvigSDK, DenvigSDKError, type DenvigSDKOptions, DenvigService, DenvigValidationError, DenvigWorktree, type GatewayStatus, ProjectConfigSchema, type ProjectInfo, type ServiceStatus as ProjectServiceStatus, type ServiceResponse, type ServiceRow, type TreeNode };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e,c as t,d as n,i as r,l as i,n as a,o,r as s,s as c,t as l,u}from"./project-BA4nj7bB.js";import{A as d,B as f,D as p,E as m,F as h,G as ee,H as g,I as _,J as v,K as y,L as b,M as x,N as S,O as te,P as C,R as w,T,U as E,V as D,W as ne,Y as re,a as O,g as k,i as A,m as j,o as M,q as N,v as P,y as F,z as I}from"./teardown-DgjT-aE7.js";import{n as L}from"./safeReadFile-Z2PHqO9j.js";import{t as R}from"./reconcile-CvY7dT3H.js";import{execSync as z}from"node:child_process";import{basename as B,resolve as V}from"node:path";import{X509Certificate as H}from"node:crypto";import{chmodSync as U,copyFileSync as W,existsSync as G,mkdirSync as K,readFileSync as q,readdirSync as J,rmSync as Y,statSync as X}from"node:fs";const Z=async({cwd:e,project:t})=>{let n=null;if(t)n=(await r(t,T)).path;else{let t=P(e);if(t)n=t;else{let t=(await j()).find(t=>e===t.path||e.startsWith(`${t.path}/`));n=t?t.path:e}}let i=n?await k.retrieve(n):null,a=n?await F(n):null;return{project:i,projectPath:n,slug:a}},ie=async(e={})=>{let t=I(),n;try{n=J(t).filter(e=>{try{return X(V(t,e)).isDirectory()}catch{return!1}})}catch{n=[]}if(n.length===0)return[];let r=await D(),i=r?q(h(),`utf-8`):null,a=r?await g():!1,o=new Date,s=[];for(let e of n){let n=x(V(t,e));if(n)try{let t=q(n,`utf-8`),r=y(t),c=b(t),l=i?E(t,i):ne(t),u=w(t),d;d=c<=o?`expired`:l&&!a?`untrusted`:`valid`,s.push({name:e,domains:r.length>0?r:[e],issuer:u,expires:c.toISOString(),status:d,signedByLocalCa:l,caTrusted:a})}catch{}}if(e.domain){let t=e.domain;return s.filter(e=>e.domains.includes(t))}return s},ae=e=>{if(e.name)return V(I(),e.name);if(e.domain)return _(e.domain);throw new n("A certificate `domain` or `name` is required.")},Q=async e=>{let t=ae(e);return!G(t)||!X(t).isDirectory()?null:{name:B(t),path:t,files:J(t)}},oe=async e=>{let{domain:t,force:r}=e;if(!await D())throw new n("The local CA is not configured. Run `ca.configure()` first.");let i=_(t);if(G(i)&&!r)throw new n(`A certificate already exists for "${t}". Pass force to overwrite.`);let{cert:a,key:o}=await ee(),{privkey:s,fullchain:c}=await C(t,a,o);return await re(t,s,c),{domain:t,name:B(i),privkey:V(i,`privkey.pem`),fullchain:V(i,`fullchain.pem`)}},$=async e=>{let t=await Q(e);if(!t)throw new n(`Certificate "${e.name??e.domain}" not found.`);return Y(t.path,{recursive:!0}),{name:t.name,files:t.files}},se=async e=>{let t=V(e.keyPath),r=V(e.certPath),i;try{i=q(r,`utf-8`)}catch{throw new n(`Could not read certificate file: ${r}`)}let a=y(i);if(a.length===0)throw new n(`Could not determine a domain from the certificate.`);let o=e.name?.replace(/^\*\./,`_wildcard.`),s=o??a[0],c=o?V(I(),o):_(s);K(c,{recursive:!0});let l=V(c,`privkey.pem`),u=V(c,`fullchain.pem`);return W(t,l),U(l,384),W(r,u),{domain:s,name:B(c),privkey:l,fullchain:u}},ce=async()=>{let e=h();if(!await D())return{initialized:!1,trusted:!1,path:e};let t=await g(),n=new H(q(e,`utf-8`));return{initialized:!0,trusted:t,path:e,subject:n.subject,issuer:n.issuer,validFrom:n.validFrom,validTo:n.validTo,serialNumber:n.serialNumber,fingerprint256:n.fingerprint256}},le=async()=>{let e=h();if(await D())return f(e),{created:!1,path:e};let{certPem:t,keyPem:n}=await S();return await v(t,n),f(e),{created:!0,path:e}},ue=async()=>{let e=h();if(!await D())throw new n("The local CA is not configured. Run `ca.configure()` first.");return N(e),{path:e}},de=()=>{try{let e=z(`brew services info nginx --json`,{stdio:[`pipe`,`pipe`,`pipe`],encoding:`utf-8`}),t=JSON.parse(e),n=Array.isArray(t)?t[0]:t;return{running:n.running??!1,pid:n.pid??null,status:n.status??null}}catch{return{running:!1,pid:null,status:null}}},fe=async(e={})=>{let t=(await m()).experimental?.gateway,n=t?.enabled??!1,r=[],{project:i}=await Z({cwd:e.cwd??process.cwd()});if(i){let e=i.activeWorktree,n=e.config.services||{};for(let[i,a]of Object.entries(n)){let n=a.http?.domain;if(!n)continue;let o=a.http?.secure??!1,s=o?await te(n):null,c=s?await d(s):null,l=t?.enabled?M(e.id,i,t.configsPath):null;r.push({name:i,domain:n,cnames:a.http?.cnames||[],port:a.http?.port,secure:o,certFound:!!c,certDir:s,nginxConfigPath:l,nginxConfigExists:l?await L(l):!1})}}return{enabled:n,handler:t?.handler??`nginx`,configsPath:t?.configsPath??null,nginxConf:t?.configsPath?O(t.configsPath):null,nginx:de(),services:r}},pe=async()=>({reconcile:await R(),gateway:await A()}),me=/^[a-z]([a-z0-9-]*[a-z0-9])?$/;var he=class{ctx;constructor(e){if(!me.test(e.client))throw Error(`Invalid client name "${e.client}". Must start with a letter, contain only lowercase alphanumeric and hyphens, and not end with a hyphen.`);this.ctx={client:e.client,cwd:e.cwd??process.cwd()}}version(){return c()}projects={retrieve:async e=>{let t=await Z({cwd:this.ctx.cwd,project:e});if(!t.project)throw new n(`Project "${e}" could not be resolved.`);return new l(t.project,this.ctx)},list:e=>o(this.ctx,`projects.list`,null,async()=>{let t=await j(e),n=new Map;for(let{path:e}of t){let t=await k.retrieve(e),r=t.primaryWorktree.path;n.has(r)||(t.activeWorktree=t.primaryWorktree,n.set(r,new l(t,this.ctx)))}return[...n.values()].sort((e,t)=>e.path.localeCompare(t.path))}),detect:async e=>{let t=await Z({cwd:this.ctx.cwd,project:e});return{project:t.project?new l(t.project,this.ctx):null,projectPath:t.projectPath,slug:t.slug}}};certs={list:e=>o(this.ctx,`certs.list`,null,()=>ie(e)),retrieve:e=>o(this.ctx,`certs.retrieve`,null,()=>Q(e)),create:e=>o(this.ctx,`certs.create`,null,()=>oe(e)),remove:e=>o(this.ctx,`certs.remove`,null,()=>$(e)),import:e=>o(this.ctx,`certs.import`,null,()=>se(e)),ca:{status:()=>o(this.ctx,`certs.ca.status`,null,()=>ce()),configure:()=>o(this.ctx,`certs.ca.configure`,null,()=>le()),remove:()=>o(this.ctx,`certs.ca.remove`,null,()=>ue())}};gateway={status:()=>o(this.ctx,`gateway.status`,null,()=>fe({cwd:this.ctx.cwd})),configure:()=>o(this.ctx,`gateway.configure`,null,()=>pe())};config={retrieve:async e=>{if(!e?.project)return m();let t=await Z({cwd:this.ctx.cwd,project:e.project});if(!t.project)throw new n(`Project "${e.project}" could not be resolved.`);return t.project.activeWorktree.config}}};export{e as DenvigAction,t as DenvigError,i as DenvigOperationError,l as DenvigProject,he as DenvigSDK,u as DenvigSDKError,s as DenvigService,n as DenvigValidationError,a as DenvigWorktree,p as ProjectConfigSchema};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./chunk-CMqjfN_6.cjs"),t=require("./teardown-DypXa0Wy.cjs"),n=require("./safeReadFile-BlLUgHuA.cjs"),r=require("./reconcile-DU2Wb6SO.cjs"),i=require("./path-pt0IDc1N.cjs");let a=require("node:child_process"),o=require("node:os"),s=require("node:path");s=e.t(s,1);let c=require("node:util");const l=(0,c.promisify)(a.execFile),u=()=>t.w(`brew`,[`update`],{stdio:`ignore`}),d=()=>t.w(`brew`,[`upgrade`]),f=async()=>{try{let{stdout:e}=await l(`brew`,[`outdated`,`--json=v2`]);return JSON.parse(e)}catch{return null}},p=(e,t)=>new Promise(n=>{let r=(0,a.spawn)(`denvig`,t,{cwd:e,stdio:`inherit`});r.on(`close`,e=>n(e===0)),r.on(`error`,()=>n(!1))}),m=s.default.join((0,o.homedir)(),`.dotfiles`),h=()=>n.t(m),g=e=>`https://github.com/${e}/dotfiles`,_=async e=>{if(await h()){let n=await t.y(m),r=t.C(e);if(n&&r&&n.toLowerCase()===r.toLowerCase())console.warn(`Warning: ${i.t(m)} already exists for ${n}, skipping clone`);else{let e=n??`(unknown remote)`;return{success:!1,message:`${i.t(m)} already exists with a different repository (${e})`}}}else if(console.log(`Cloning ${e} into ${i.t(m)}...`),!await t.b(e,m))return{success:!1,message:`Failed to clone dotfiles repo`};return console.log(``),console.log(`Running denvig setup in ${i.t(m)}...`),await p(m,[`run`,`setup`])?{success:!0,message:`Dotfiles installed`}:{success:!1,message:`Setup action failed`}},v=(0,c.promisify)(a.execFile),y=async()=>{try{let{stdout:e}=await v(`fdesetup`,[`status`]);return/FileVault is On/i.test(e)}catch{return!1}},b=()=>t.w(`sudo`,[`fdesetup`,`enable`]),x=(0,c.promisify)(a.execFile),S=async e=>{try{let{stdout:t}=await x(`git`,[`config`,`--global`,e]);return t.trim()||null}catch{return null}},C=async()=>{let[e,t]=await Promise.all([S(`user.name`),S(`user.email`)]);return!e||!t?null:{name:e,email:t}},w=async(e,n)=>await t.w(`git`,[`config`,`--global`,`user.name`,e])?t.w(`git`,[`config`,`--global`,`user.email`,n]):!1,T=()=>n.n(`/opt/homebrew/bin/brew`),E=()=>t.w(`sh`,[`-c`,`/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`]),D=`/opt/homebrew/bin/skills`,O=()=>n.n(D),k=()=>t.w(D,[`update`,`-g`]),A=`/etc/pam.d/sudo_local`,j=/^\s*auth\s+sufficient\s+pam_tid\.so/m,M=async()=>{for(let e of[A,`/etc/pam.d/sudo`]){let t=await n.r(e);if(t&&j.test(t))return!0}return!1},N=()=>t.w(`sh`,[`-c`,`echo 'auth sufficient pam_tid.so' | sudo tee -a ${A} > /dev/null`]),P=(0,c.promisify)(a.execFile),F=async()=>{try{return await P(`xcode-select`,[`-p`]),!0}catch{return!1}},I=()=>t.w(`xcode-select`,[`--install`]);exports.ServiceManager=t.r,exports.areDotfilesInstalled=h,exports.brewUpdate=u,exports.brewUpgrade=d,exports.buildReverseChain=t.at,exports.configureGit=w,exports.countCertsExpiringWithin=t.j,exports.createCliLogTracker=t.st,exports.dotfilesUrlForUsername=g,exports.enableFullDiskEncryption=b,exports.enableSudoTouchId=N,exports.findCertForDomain=t.O,exports.generateCaCert=t.N,exports.generateMissingCerts=t.k,exports.getBrewOutdated=f,exports.getCaCertPath=t.F,exports.getGatewayRoute=t.s,exports.getGitIdentity=C,exports.getGlobalConfig=t.E,exports.getNginxConfigPath=t.o,exports.gitPull=t.x,exports.hasSkillsCli=O,exports.installCaToKeychain=t.B,exports.installDotfiles=_,exports.installHomebrew=E,exports.installXcodeCli=I,exports.isCaInitialized=t.V,exports.isDevDependenciesSource=t.ot,exports.isFullDiskEncryptionEnabled=y,exports.isHomebrewInstalled=T,exports.isSudoTouchIdEnabled=M,exports.isWorkingTreeDirty=t.S,exports.isXcodeCliInstalled=F,exports.launchctl=t.X,exports.reconcileServices=r.t,exports.runDenvig=p,exports.skillsUpdateGlobal=k,exports.teardownGlobal=t.t,exports.writeCaFiles=t.J;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { C as _default, S as LaunchctlListItem, T as getGlobalConfig, b as gitPull, i as ServiceManagerProject, n as teardownGlobal, o as buildReverseChain, r as ServiceManager, s as isDevDependenciesSource, x as isWorkingTreeDirty } from "./teardown-BuHyVdhH.js";
|
|
2
|
+
import { n as reconcileServices } from "./reconcile-C90QGHEJ.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import forge from "node-forge";
|
|
5
|
+
|
|
6
|
+
//#region src/lib/certs.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Path to the CA certificate.
|
|
9
|
+
*/
|
|
10
|
+
declare const getCaCertPath: () => string;
|
|
11
|
+
/**
|
|
12
|
+
* Generate a new self-signed CA certificate with an RSA 2048 key pair.
|
|
13
|
+
* Valid for 10 years with basicConstraints CA:true.
|
|
14
|
+
*/
|
|
15
|
+
declare const generateCaCert: () => Promise<{
|
|
16
|
+
cert: forge.pki.Certificate;
|
|
17
|
+
key: forge.pki.rsa.PrivateKey;
|
|
18
|
+
certPem: string;
|
|
19
|
+
keyPem: string;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Install the CA certificate into the macOS system keychain.
|
|
23
|
+
* Requires sudo for write access to the System keychain.
|
|
24
|
+
*/
|
|
25
|
+
declare const installCaToKeychain: (caCertPath: string) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Write CA files to disk, creating directories as needed.
|
|
28
|
+
* The private key is written with mode 0600.
|
|
29
|
+
*/
|
|
30
|
+
declare const writeCaFiles: (certPem: string, keyPem: string) => Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Check if the CA has been initialized by verifying both
|
|
33
|
+
* the certificate and key files exist on disk.
|
|
34
|
+
*/
|
|
35
|
+
declare const isCaInitialized: () => Promise<boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Count managed certificates whose expiry is at or before `now + durationMs`.
|
|
38
|
+
* Already-expired certificates are included in the count.
|
|
39
|
+
* Returns 0 if the certs directory is missing or unreadable.
|
|
40
|
+
*/
|
|
41
|
+
declare const countCertsExpiringWithin: (durationMs: number, now?: Date) => number;
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/lib/cli-logs.d.ts
|
|
44
|
+
/**
|
|
45
|
+
* Create a CLI log entry helper.
|
|
46
|
+
* Call start() before command execution and finish() after.
|
|
47
|
+
*/
|
|
48
|
+
declare const createCliLogTracker: (options: {
|
|
49
|
+
version: string;
|
|
50
|
+
command: string;
|
|
51
|
+
path: string;
|
|
52
|
+
slug?: string;
|
|
53
|
+
via?: string;
|
|
54
|
+
}) => {
|
|
55
|
+
/**
|
|
56
|
+
* Complete the log entry and write it to the log file.
|
|
57
|
+
* @param status - Exit code (0 for success, non-zero for failure)
|
|
58
|
+
* @param error - Optional error message for non-zero exit codes
|
|
59
|
+
*/
|
|
60
|
+
finish: (status: number, error?: string) => Promise<void>;
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/lib/gateway/certs.d.ts
|
|
64
|
+
/**
|
|
65
|
+
* Find an existing cert directory that covers the given domain.
|
|
66
|
+
* Scans all cert directories in `~/.denvig/certs/`, reads each `fullchain.pem`,
|
|
67
|
+
* and checks for an exact or wildcard match.
|
|
68
|
+
* @returns The cert directory path if found, null otherwise.
|
|
69
|
+
*/
|
|
70
|
+
declare function findCertForDomain(domain: string): Promise<string | null>;
|
|
71
|
+
/**
|
|
72
|
+
* Generate missing certs for a list of domains.
|
|
73
|
+
* Checks existing certs first via `findCertForDomain`, then groups uncovered
|
|
74
|
+
* domains by parent and generates wildcard certs where appropriate.
|
|
75
|
+
* @returns Map from domain to cert directory path.
|
|
76
|
+
*/
|
|
77
|
+
declare function generateMissingCerts(domains: string[]): Promise<Map<string, string>>;
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/lib/gateway/nginx.d.ts
|
|
80
|
+
/**
|
|
81
|
+
* Get config file path for a service.
|
|
82
|
+
* Format: {configsPath}/denvig.{projectId}.{serviceName}.conf
|
|
83
|
+
*/
|
|
84
|
+
declare function getNginxConfigPath(projectId: string, serviceName: string, configsPath: string): string;
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/lib/services/state.d.ts
|
|
87
|
+
/**
|
|
88
|
+
* One mapping in the gateway routing table. `defaultService` records
|
|
89
|
+
* whether the route was registered as the natural owner of the domain
|
|
90
|
+
* (`true`, the first claim) or a worktree override (`false`, claimed via
|
|
91
|
+
* the conflict prompt).
|
|
92
|
+
*
|
|
93
|
+
* `cert` is the key into `state.certs` for the cert this route should
|
|
94
|
+
* present in nginx. Only populated for secure routes whose cert was
|
|
95
|
+
* resolved at start time.
|
|
96
|
+
*/
|
|
97
|
+
declare const GatewayRouteSchema: z.ZodObject<{
|
|
98
|
+
project: z.ZodString;
|
|
99
|
+
service: z.ZodString;
|
|
100
|
+
port: z.ZodNumber;
|
|
101
|
+
defaultService: z.ZodDefault<z.ZodBoolean>;
|
|
102
|
+
secure: z.ZodDefault<z.ZodBoolean>;
|
|
103
|
+
desiredStatus: z.ZodDefault<z.ZodEnum<{
|
|
104
|
+
running: "running";
|
|
105
|
+
stopped: "stopped";
|
|
106
|
+
}>>;
|
|
107
|
+
cert: z.ZodOptional<z.ZodString>;
|
|
108
|
+
}, z.core.$strip>;
|
|
109
|
+
type GatewayRoute = z.infer<typeof GatewayRouteSchema>;
|
|
110
|
+
/** Get the gateway route for a domain, or null when none is recorded. */
|
|
111
|
+
declare const getGatewayRoute: (domain: string) => Promise<GatewayRoute | null>;
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/lib/system/brew.d.ts
|
|
114
|
+
type BrewOutdatedFormula = {
|
|
115
|
+
name: string;
|
|
116
|
+
installed_versions?: string[];
|
|
117
|
+
current_version?: string;
|
|
118
|
+
};
|
|
119
|
+
type BrewOutdatedCask = {
|
|
120
|
+
name: string;
|
|
121
|
+
installed_versions?: string;
|
|
122
|
+
current_version?: string;
|
|
123
|
+
};
|
|
124
|
+
type BrewOutdatedJson = {
|
|
125
|
+
formulae?: BrewOutdatedFormula[];
|
|
126
|
+
casks?: BrewOutdatedCask[];
|
|
127
|
+
};
|
|
128
|
+
/** Run `brew update` silently. Resolves to exit success. */
|
|
129
|
+
declare const brewUpdate: () => Promise<boolean>;
|
|
130
|
+
/** Run `brew upgrade` with inherited stdio. */
|
|
131
|
+
declare const brewUpgrade: () => Promise<boolean>;
|
|
132
|
+
/** Read outdated brew packages as parsed JSON, or null if the call fails. */
|
|
133
|
+
declare const getBrewOutdated: () => Promise<BrewOutdatedJson | null>;
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/lib/system/denvig.d.ts
|
|
136
|
+
/**
|
|
137
|
+
* Spawn a child denvig process in the given directory and inherit stdio.
|
|
138
|
+
* Resolves to whether the command exited successfully.
|
|
139
|
+
*/
|
|
140
|
+
declare const runDenvig: (cwd: string, args: string[]) => Promise<boolean>;
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/lib/system/dotfiles.d.ts
|
|
143
|
+
/** Whether the `~/.dotfiles` directory exists. */
|
|
144
|
+
declare const areDotfilesInstalled: () => Promise<boolean>;
|
|
145
|
+
/** Build the canonical dotfiles repo URL for a GitHub username. */
|
|
146
|
+
declare const dotfilesUrlForUsername: (username: string) => string;
|
|
147
|
+
type InstallDotfilesResult = {
|
|
148
|
+
success: boolean;
|
|
149
|
+
message: string;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Clone the dotfiles repo to `~/.dotfiles` (or skip if already present and
|
|
153
|
+
* the remote matches), then run `denvig run setup` inside it.
|
|
154
|
+
*/
|
|
155
|
+
declare const installDotfiles: (url: string) => Promise<InstallDotfilesResult>;
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/lib/system/fullDiskEncryption.d.ts
|
|
158
|
+
/** Whether macOS FileVault is currently turned on. */
|
|
159
|
+
declare const isFullDiskEncryptionEnabled: () => Promise<boolean>;
|
|
160
|
+
/** Run `sudo fdesetup enable` interactively. */
|
|
161
|
+
declare const enableFullDiskEncryption: () => Promise<boolean>;
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/lib/system/gitConfig.d.ts
|
|
164
|
+
type GitIdentity = {
|
|
165
|
+
name: string;
|
|
166
|
+
email: string;
|
|
167
|
+
};
|
|
168
|
+
/** Read `user.name` and `user.email` from the global git config. */
|
|
169
|
+
declare const getGitIdentity: () => Promise<GitIdentity | null>;
|
|
170
|
+
/** Set `user.name` and `user.email` in global git config. */
|
|
171
|
+
declare const configureGit: (name: string, email: string) => Promise<boolean>;
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/lib/system/homebrew.d.ts
|
|
174
|
+
/** Whether Homebrew is installed at the standard Apple Silicon path. */
|
|
175
|
+
declare const isHomebrewInstalled: () => Promise<boolean>;
|
|
176
|
+
/** Run the official Homebrew install script. */
|
|
177
|
+
declare const installHomebrew: () => Promise<boolean>;
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/lib/system/skills.d.ts
|
|
180
|
+
/** Whether the `skills` CLI is installed at the well-known Homebrew path. */
|
|
181
|
+
declare const hasSkillsCli: () => Promise<boolean>;
|
|
182
|
+
/** Run `skills update -g`. */
|
|
183
|
+
declare const skillsUpdateGlobal: () => Promise<boolean>;
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/lib/system/sudoTouchId.d.ts
|
|
186
|
+
/** Whether `pam_tid.so` is configured for sudo (i.e. Touch ID is enabled). */
|
|
187
|
+
declare const isSudoTouchIdEnabled: () => Promise<boolean>;
|
|
188
|
+
/**
|
|
189
|
+
* Append the `pam_tid.so` line to `/etc/pam.d/sudo_local`. Requires sudo.
|
|
190
|
+
*/
|
|
191
|
+
declare const enableSudoTouchId: () => Promise<boolean>;
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/lib/system/xcodeCli.d.ts
|
|
194
|
+
/** Whether the Xcode Command Line Tools are installed. */
|
|
195
|
+
declare const isXcodeCliInstalled: () => Promise<boolean>;
|
|
196
|
+
/**
|
|
197
|
+
* Trigger the Xcode Command Line Tools installer.
|
|
198
|
+
* This pops up a system dialog; installation continues asynchronously.
|
|
199
|
+
*/
|
|
200
|
+
declare const installXcodeCli: () => Promise<boolean>;
|
|
201
|
+
//#endregion
|
|
202
|
+
export { type GatewayRoute, type LaunchctlListItem, ServiceManager, type ServiceManagerProject, areDotfilesInstalled, brewUpdate, brewUpgrade, buildReverseChain, configureGit, countCertsExpiringWithin, createCliLogTracker, dotfilesUrlForUsername, enableFullDiskEncryption, enableSudoTouchId, findCertForDomain, generateCaCert, generateMissingCerts, getBrewOutdated, getCaCertPath, getGatewayRoute, getGitIdentity, getGlobalConfig, getNginxConfigPath, gitPull, hasSkillsCli, installCaToKeychain, installDotfiles, installHomebrew, installXcodeCli, isCaInitialized, isDevDependenciesSource, isFullDiskEncryptionEnabled, isHomebrewInstalled, isSudoTouchIdEnabled, isWorkingTreeDirty, isXcodeCliInstalled, _default as launchctl, reconcileServices, runDenvig, skillsUpdateGlobal, teardownGlobal, writeCaFiles };
|
package/dist/internal.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{B as e,C as t,E as n,F as r,J as i,N as a,O as o,S as s,V as c,X as l,at as u,b as d,j as f,k as p,o as m,ot as h,r as g,s as _,st as v,t as y,w as b,x,y as S}from"./teardown-DgjT-aE7.js";import{n as C,r as w,t as T}from"./safeReadFile-Z2PHqO9j.js";import{t as E}from"./reconcile-CvY7dT3H.js";import{t as D}from"./path-DElA4WIM.js";import{execFile as O,spawn as k}from"node:child_process";import{homedir as A}from"node:os";import j from"node:path";import{promisify as M}from"node:util";const N=M(O),P=()=>b(`brew`,[`update`],{stdio:`ignore`}),F=()=>b(`brew`,[`upgrade`]),ee=async()=>{try{let{stdout:e}=await N(`brew`,[`outdated`,`--json=v2`]);return JSON.parse(e)}catch{return null}},I=(e,t)=>new Promise(n=>{let r=k(`denvig`,t,{cwd:e,stdio:`inherit`});r.on(`close`,e=>n(e===0)),r.on(`error`,()=>n(!1))}),L=j.join(A(),`.dotfiles`),R=()=>T(L),z=e=>`https://github.com/${e}/dotfiles`,B=async e=>{if(await R()){let n=await S(L),r=t(e);if(n&&r&&n.toLowerCase()===r.toLowerCase())console.warn(`Warning: ${D(L)} already exists for ${n}, skipping clone`);else{let e=n??`(unknown remote)`;return{success:!1,message:`${D(L)} already exists with a different repository (${e})`}}}else if(console.log(`Cloning ${e} into ${D(L)}...`),!await d(e,L))return{success:!1,message:`Failed to clone dotfiles repo`};return console.log(``),console.log(`Running denvig setup in ${D(L)}...`),await I(L,[`run`,`setup`])?{success:!0,message:`Dotfiles installed`}:{success:!1,message:`Setup action failed`}},V=M(O),H=async()=>{try{let{stdout:e}=await V(`fdesetup`,[`status`]);return/FileVault is On/i.test(e)}catch{return!1}},U=()=>b(`sudo`,[`fdesetup`,`enable`]),W=M(O),G=async e=>{try{let{stdout:t}=await W(`git`,[`config`,`--global`,e]);return t.trim()||null}catch{return null}},K=async()=>{let[e,t]=await Promise.all([G(`user.name`),G(`user.email`)]);return!e||!t?null:{name:e,email:t}},q=async(e,t)=>await b(`git`,[`config`,`--global`,`user.name`,e])?b(`git`,[`config`,`--global`,`user.email`,t]):!1,J=()=>C(`/opt/homebrew/bin/brew`),Y=()=>b(`sh`,[`-c`,`/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`]),X=`/opt/homebrew/bin/skills`,Z=()=>C(X),Q=()=>b(X,[`update`,`-g`]),$=`/etc/pam.d/sudo_local`,te=/^\s*auth\s+sufficient\s+pam_tid\.so/m,ne=async()=>{for(let e of[$,`/etc/pam.d/sudo`]){let t=await w(e);if(t&&te.test(t))return!0}return!1},re=()=>b(`sh`,[`-c`,`echo 'auth sufficient pam_tid.so' | sudo tee -a ${$} > /dev/null`]),ie=M(O),ae=async()=>{try{return await ie(`xcode-select`,[`-p`]),!0}catch{return!1}},oe=()=>b(`xcode-select`,[`--install`]);export{g as ServiceManager,R as areDotfilesInstalled,P as brewUpdate,F as brewUpgrade,u as buildReverseChain,q as configureGit,f as countCertsExpiringWithin,v as createCliLogTracker,z as dotfilesUrlForUsername,U as enableFullDiskEncryption,re as enableSudoTouchId,o as findCertForDomain,a as generateCaCert,p as generateMissingCerts,ee as getBrewOutdated,r as getCaCertPath,_ as getGatewayRoute,K as getGitIdentity,n as getGlobalConfig,m as getNginxConfigPath,x as gitPull,Z as hasSkillsCli,e as installCaToKeychain,B as installDotfiles,Y as installHomebrew,oe as installXcodeCli,c as isCaInitialized,h as isDevDependenciesSource,H as isFullDiskEncryptionEnabled,J as isHomebrewInstalled,ne as isSudoTouchIdEnabled,s as isWorkingTreeDirty,ae as isXcodeCliInstalled,l as launchctl,E as reconcileServices,I as runDenvig,Q as skillsUpdateGlobal,y as teardownGlobal,i as writeCaFiles};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=e=>e.replace(process.env.HOME||`/root`,`~`);export{e as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=e=>e.replace(process.env.HOME||`/root`,`~`);Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return e}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,Q as t,T as n,X as r,Z as i,d as a,et as o,f as s,g as c,it as l,m as u,n as d,nt as f,p,r as m,rt as h,st as g,tt as _}from"./teardown-DgjT-aE7.js";import{n as v}from"./semver-dZSpezIR.js";import{spawn as y}from"node:child_process";import{createHash as ee}from"node:crypto";var b=class extends Error{constructor(e){super(e),this.name=`DenvigError`}},x=class extends b{constructor(e){super(e),this.name=`DenvigValidationError`}},S=class extends b{details;constructor(e,t){super(e),this.name=`DenvigOperationError`,this.details=t}},C=class extends Error{originalMessage;stderr;stdout;constructor(e,t,n,r){super(e),this.name=`DenvigSDKError`,this.originalMessage=t,this.stderr=n,this.stdout=r}};const w=async(e,t)=>{let{args:n=[],projectSlug:r,cwd:i}=t,a=t.onCommand??(e=>console.log(e)),o=t.interactive??!!(process.stdout.isTTY&&process.stdin.isTTY),s={success:!0};for(let t of e){let e=`${t} ${n.join(` `)}`.trim();a(`$ ${e}`);let c={...process.env,DENVIG_PROJECT:r},l,u;o?process.platform===`darwin`?(l=`script`,u=[`-q`,`/dev/null`,`sh`,`-c`,e]):(l=`script`,u=[`-q`,`-c`,e,`/dev/null`]):(l=`sh`,u=[`-c`,e]);let d=y(l,u,{cwd:i,env:c,stdio:`inherit`}),f=await new Promise(e=>{d.on(`close`,t=>{e({success:t===0})})});f.success||(s=f)}return s};var T=`0.7.0-alpha.4`;function E(){return T}const D=async(e,t,n,r)=>{let i=g({version:E(),command:`sdk:${t}`,path:e.cwd,slug:n??void 0,via:`sdk:${e.client}`});try{let e=await r();return await i.finish(0),e}catch(e){let t=e instanceof Error?e.message:String(e);throw await i.finish(1,t.replace(/[\r\n]+/g,` `).trim()),e}};var O=class{_name;_commands;worktree;ctx;constructor(e,t,n,r){this._name=e,this._commands=t,this.worktree=n,this.ctx=r}get name(){return this._name}get commands(){return this._commands}async run(e){return D(this.ctx,`actions.run`,this.worktree.slug,()=>w(this._commands,{args:e?.args,projectSlug:this.worktree.slug,cwd:this.worktree.path}))}};const k=async e=>{let t=[];if(e.config.actions)for(let[n,r]of Object.entries(e.config.actions))t.push({name:n,ecosystem:`project`,commands:[r.command]});for(let[n,r]of Object.entries(i)){let i=await r.actions(e);for(let[e,a]of Object.entries(i))t.push({name:e,ecosystem:r.name??n,commands:a})}return t},A=(e,t,n)=>{let r=t,i=n,a=t.indexOf(`:`);a!==-1&&!e.some(e=>e.name===t)&&(i=t.slice(0,a),r=t.slice(a+1));let o=e.filter(e=>e.name===r);if(o.length===0)throw new x(`Action "${t}" not found.`);if(i){let e=o.find(e=>e.ecosystem===i);if(!e)throw new x(`Action "${r}" not found for ecosystem "${i}".`);return e}return{name:r,ecosystem:o[0].ecosystem,commands:o.flatMap(e=>e.commands)}},j=async(e,t)=>{let n=e.activeWorktree,i=n.config.$sources.length>0,a=t?.includeServiceStatus??!0,{$sources:o,...s}=n.config,c=`none`;if(a){let e=n.config.services||{},i=Object.keys(e);if(i.length>0){let e=t?.launchctlList??await r.list(`denvig.`),a=new m(n),o=!1;for(let t of i)if((await a.getServiceResponse(t,{launchctlList:e}))?.status===`running`){o=!0;break}c=o?`running`:`stopped`}}return{id:n.id,slug:n.slug,name:n.name,path:n.path,refs:n.refs,worktrees:e.worktrees.filter(e=>!e.isPrimary).map(e=>({path:e.path,branch:e.branch})),config:i?s:null,serviceStatus:c}},M=e=>{let{project:t,workspace:n=`root`,resource:r}=e;if(!r.startsWith(`action/`)&&!r.startsWith(`service/`))throw Error(`Invalid resource format: ${r}. Must start with "action/" or "service/".`);return`@${t.slug}|${n}|${r}`},N=e=>{let t=M(e);return{id:t,hash:ee(`sha256`).update(t).digest(`hex`)}},P=e=>{if(e.startsWith(`id:`)){let t=e.slice(3),n=t.indexOf(`/`);return n===-1?{type:`id`,value:t}:{type:`id`,value:t.slice(0,n),serviceName:t.slice(n+1)}}if(e.startsWith(`github:`)){let t=e.slice(7),n=t.split(`/`);return n.length<=2?{type:`github`,value:t}:{type:`github`,value:`${n[0]}/${n[1]}`,serviceName:n.slice(2).join(`/`)}}if(e.startsWith(`local:`))return{type:`local`,value:e.slice(6)};if(e.startsWith(`/`)||e.startsWith(`~`))return{type:`path`,value:e};let t=e.split(`/`);return t.length<=2?{type:`github`,value:e}:{type:`github`,value:`${t[0]}/${t[1]}`,serviceName:t.slice(2).join(`/`)}},F=async(e,t)=>{let n=await u();switch(e.type){case`id`:for(let t of n){let n=await c.retrieve(t.path);if(n.id===e.value||n.id.startsWith(e.value))return t.path}return null;case`github`:{let t=n.find(t=>t.slug===`github:${e.value}`);return t?t.path:null}case`local`:{let r=t(e.value),i=n.find(e=>e.path===r);return i?i.path:r}case`path`:return t(e.value)}},I=async(e,t)=>{let n=P(e);return{path:await F(n,t),serviceName:n.serviceName}},L=(e,t)=>{if(e.startsWith(`global:`))return{projectSlug:`global`,serviceName:e.slice(7)};if(!e.includes(`/`))return{projectSlug:t,serviceName:e};let n=P(e);if(n.serviceName!==void 0)return n.type===`id`?{projectSlug:``,projectId:n.value,serviceName:n.serviceName}:{projectSlug:n.type===`local`?`local:${n.value}`:`github:${n.value}`,serviceName:n.serviceName};if(n.type===`path`||n.type===`local`)return{projectSlug:n.type===`local`?`local:${n.value}`:n.value,serviceName:``};let r=e.split(`/`),i=r.pop();return{projectSlug:r.join(`/`),serviceName:i}},R=async e=>{let t=await u({withConfig:!0}),n=t.find(t=>t.slug===e);if(n)return n.path;let r=t.find(t=>t.slug.replace(/^(github|local):/,``)===e);return r?r.path:null},z=async e=>{let t=await u({withConfig:!0});for(let n of t){let t=await c.retrieve(n.path);if(t.id===e||t.id.startsWith(e))return n.path}return null},B=async(e,t)=>{if(e.startsWith(`global:`)){let t=e.slice(7);return{project:await a(),manager:await s(),serviceName:t}}let r=P(e);if(r.serviceName!==void 0&&r.serviceName!==``){let e=await F(r,n);if(e){let t=(await c.retrieve(e)).activeWorktree;return{project:t,manager:new m(t),serviceName:r.serviceName}}}let{projectSlug:i,projectId:o,serviceName:l}=L(e,t.slug),u;if(p(i))return{project:await a(),manager:await s(),serviceName:l};if(o)if(t.id===o||t.id.startsWith(o))u=t.activeWorktree;else{let e=await z(o);if(e)u=(await c.retrieve(e)).activeWorktree;else throw Error(`Project with ID "${o}" not found`)}else if(i===t.slug)u=t.activeWorktree;else{let e=await R(i);u=e?(await c.retrieve(e)).activeWorktree:(await c.retrieve(i)).activeWorktree}let d=new m(u);return{project:u,manager:d,serviceName:l}},V=(e,t)=>{let n=e.worktree(t);if(!n){let n=e.worktrees.filter(e=>!e.isPrimary).map(e=>e.branch),r=n.length?` Available worktrees: ${n.join(`, `)}`:``;throw Error(`Worktree with branch "${t}" not found.${r}`)}return n},H={npm:async(e,t)=>{let n=await h(e,t);return n?{ecosystem:`npm`,name:e,...n}:null},jsr:async(e,t)=>{let n=await f(e,t);return n?{ecosystem:`jsr`,name:e,...n}:null},pypi:async(e,n)=>{let r=await t(e,n);return r?{ecosystem:`pypi`,name:e,...r}:null},rubygems:async(t,n)=>{let r=await e(t,n);return r?{ecosystem:`rubygems`,name:t,...r}:null}},U=async(e,t={})=>{let n=e.indexOf(`:`);if(n<=0)throw new x(`Invalid dependency identifier "${e}". Expected "<ecosystem>:<name>" (e.g. "npm:redis").`);let r=e.slice(0,n),i=e.slice(n+1),a=H[r];if(!a)throw new x(`Unsupported ecosystem "${r}". Supported: ${Object.keys(H).join(`, `)}.`);if(!i)throw new x(`Invalid dependency identifier "${e}". Missing package name.`);return a(i,t.noCache)},W=e=>e.dependencies(),G=e=>e.versions[0]?.resolved||``,K=async(e,t={})=>{let{semver:n,ecosystem:r}=t,i=t.cache??!0;if(n&&n!==`patch`&&n!==`minor`&&n!==`major`)throw new x(`Invalid semver value: "${n}". Must be "patch", "minor", or "major".`);let a=await e.outdatedDependencies({cache:i});r&&(a=a.filter(e=>e.ecosystem===r)),n&&(a=a.filter(e=>v({currentVersion:G(e),wantedVersion:e.wanted,latestVersion:e.latest},n)));let s=null,c=[],l=t.releaseLatency??`auto`;if(l===`0`)s=null;else if(l===`auto`){let t=await o(e.path);t?(s=t.minimumReleaseAgeMs,c=t.exclude):s=1440*60*1e3}else if(s=_(l),s===null)throw new x(`Invalid releaseLatency value: "${l}". Use a duration like "3h", "7d", "2w", or "auto".`);if(s!==null){let e=s,t=Date.now();a=a.filter(n=>{if(c.includes(n.name))return!0;let r=G(n),i=n.wanted!==r,a=n.latest!==r,o=i&&n.wantedDate?t-new Date(n.wantedDate).getTime()>=e:!1,s=a&&n.latestDate?t-new Date(n.latestDate).getTime()>=e:!1;return i&&n.wantedDate||a&&n.latestDate?o||s:!0})}return a.sort((e,t)=>{let n=e.ecosystem.localeCompare(t.ecosystem);return n===0?e.name.localeCompare(t.name):n})},q=async e=>{let t={};for(let[n,r]of Object.entries(i)){let i=await r.actions(e);t[n]={name:r.name,actions:i}}return t},J=new Set([`running`,`stopped`,`error`]),Y=e=>{if(!e)return null;let t=(Array.isArray(e)?e:e.split(`,`)).map(e=>e.trim().toLowerCase()),n=t.filter(e=>!J.has(e));if(n.length>0)throw new x(`Invalid status value(s): ${n.join(`, `)}. Allowed: running, stopped, error.`);return t},X=async(e,t,n={})=>{let i=!!n.all,o=!!n.global,s=!!n.worktrees,l=n.worktree??null,d=Y(n.status);if((i||o)&&l!==null)throw new x(`worktree cannot be combined with all or global.`);if(s&&(o||l!==null))throw new x(`worktrees cannot be combined with global or worktree.`);if(i&&o)throw new x(`Cannot combine all and global. Choose one.`);if(!i&&!o&&(!e||!t))throw new x(`No project provided or detected.`);let f=t;l!==null&&e&&(f=V(e,l));let p=await r.list(`denvig.`),h=async(e,t,n,r)=>{let i=await e.listServices();i.sort((e,t)=>e.name.localeCompare(t.name));for(let a of i){let i=await e.getServiceResponse(a.name,{launchctlList:p});i&&r.push({service:i,depth:t,label:n})}},g=async e=>{let t=new Map;for(let n of await e.listServices()){let r=await e.getServiceResponse(n.name,{launchctlList:p});r&&t.set(n.name,r)}return t},_=async(e,t)=>{let n=e.primaryWorktree,r=await g(new m(n));if(!s){for(let e of[...r.keys()].sort()){let i=r.get(e);i&&t.push({service:i,depth:0,label:n.slug})}return}let i=[];for(let t of e.worktrees.filter(e=>!e.isPrimary))i.push({branch:t.branch,services:await g(new m(t))});let a=new Set(r.keys());for(let e of i)for(let t of e.services.keys())a.add(t);for(let e of[...a].sort()){let a=r.get(e);a&&t.push({service:a,depth:0,label:n.slug});for(let n of i){let r=n.services.get(e);r&&t.push({service:r,depth:1,label:n.branch})}}},v=async e=>{let t=new Map;for(let n of e){let e=await c.retrieve(n),r=e.primaryWorktree.path;t.has(r)||t.set(r,e)}return[...t.values()].sort((e,t)=>e.primaryWorktree.slug.localeCompare(t.primaryWorktree.slug))},y=[];if(o){let e=await a();Object.keys(e.config.services||{}).length>0&&await h(new m(e),0,e.slug,y)}else if(i){let e=await v((await u()).map(e=>e.path));for(let t of e)await _(t,y);let t=await a();Object.keys(t.config.services||{}).length>0&&await h(new m(t),0,t.slug,y)}else if(s)await _(e,y);else{let e=f;await h(new m(e),0,e.slug,y)}return d?y.filter(e=>d.includes(e.service.status)):y},Z=async(e,t,n)=>(n&&(e.activeWorktree=V(e,n)),B(t,e)),te=async(e,t,n={})=>{let{manager:r,serviceName:i}=await Z(e,t,n.worktree),a=await r.getServiceResponse(i,{includeLogs:n.includeLogs??!1});if(!a)throw new x(`Service "${i}" not found in configuration.`);return a},ne=()=>new Promise(e=>setTimeout(e,2e3)),re=async(e,t,n={})=>{let{manager:r,serviceName:i,project:a}=await Z(e,t,n.worktree),o=await r.resolveServicePort(i,{}),s=await r.startService(i,{port:o.success?o.port:void 0,portResolved:!0});if(!s.success)throw new S(s.message,{service:i,project:a.slug});await ne();let c=await r.getServiceResponse(i,{includeLogs:!0});if(c?.status===`running`&&await r.reconfigureGateway(),!c)throw new S(`Service failed to start.`,{service:i,project:a.slug});return c},ie=async(e,t,n={})=>{let{manager:r,serviceName:i,project:a}=await Z(e,t,n.worktree),o=await r.stopService(i);if(!o.success)throw new S(o.message,{service:i,project:a.slug});let s=await r.getServiceResponse(i);if(!s)throw new S(`Service "${i}" not found after stopping.`,{service:i,project:a.slug});return s};var Q=class{project;serviceName;worktreeName;ctx;constructor(e,t,n,r){this.project=e,this.serviceName=t,this.worktreeName=n,this.ctx=r}get name(){return this.serviceName}async start(){return D(this.ctx,`services.start`,this.project.slug,()=>re(this.project,this.serviceName,{worktree:this.worktreeName}))}async stop(){return D(this.ctx,`services.stop`,this.project.slug,()=>ie(this.project,this.serviceName,{worktree:this.worktreeName}))}async status(){return D(this.ctx,`services.status`,this.project.slug,()=>te(this.project,this.serviceName,{worktree:this.worktreeName,includeLogs:!0}))}},$=class{_internal;constructor(e){this._internal=e}get internal(){return this._internal}get name(){return this._internal.name}get branch(){return this._internal.branch}get path(){return this._internal.path}get slug(){return this._internal.slug}get id(){return this._internal.id}get isPrimary(){return this._internal.isPrimary}get config(){return this._internal.config}get services(){return this._internal.services}get actions(){return this._internal.actions}dependencies(){return this._internal.dependencies()}outdatedDependencies(e){return this._internal.outdatedDependencies(e)}deduplicateDependencies(e){return this._internal.deduplicateDependencies(e)}},ae=class{internal;ctx;constructor(e,t){this.internal=e,this.ctx=t}get id(){return this.internal.id}get slug(){return this.internal.slug}get name(){return this.internal.name}get path(){return this.internal.path}get refs(){return this.internal.refs}get activeWorktree(){return new $(this.internal.activeWorktree)}get primaryWorktree(){return new $(this.internal.primaryWorktree)}worktreeFor(e){return e?V(this.internal,e):this.internal.activeWorktree}selectWorktree(e){let t=V(this.internal,e);return this.internal.activeWorktree=t,new $(t)}info(e){return D(this.ctx,`projects.info`,this.internal.slug,()=>j(this.internal,e))}plugins(){return D(this.ctx,`plugins.list`,this.internal.slug,()=>q(this.internal.activeWorktree))}teardown(e){return D(this.ctx,`projects.teardown`,this.internal.slug,()=>d(this.internal,e))}resourceId(e){return M({project:this.internal,...e})}resourceHash(e){return N({project:this.internal,...e})}worktrees={list:()=>this.internal.worktrees.map(e=>new $(e)),retrieve:e=>new $(V(this.internal,e))};actions={retrieve:async(e,t)=>{let n=this.worktreeFor(t?.worktree),r=A(await k(n),e,t?.ecosystem);return new O(r.name,r.commands,n,this.ctx)}};services={retrieve:async(e,t)=>new Q(this.internal,e,t?.worktree,this.ctx),list:e=>D(this.ctx,`services.list`,this.internal.slug,()=>X(this.internal,this.internal.activeWorktree,e)),context:async e=>{let{manager:t,serviceName:n,project:r}=await B(e,this.internal);return{manager:t,serviceName:n,target:r}}};dependencies={list:()=>D(this.ctx,`dependencies.list`,this.internal.slug,()=>W(this.internal.activeWorktree)),tree:e=>D(this.ctx,`dependencies.tree`,this.internal.slug,async()=>l(await W(this.internal.activeWorktree),e?.depth??0,e?.ecosystem)),info:(e,t)=>D(this.ctx,`dependencies.info`,this.internal.slug,()=>U(e,t)),retrieve:async e=>{let t=(await W(this.internal.activeWorktree)).find(t=>t.id===e);if(!t)throw new x(`Dependency "${e}" not found.`);return t},outdated:e=>{let t=this.worktreeFor(e?.worktree);return D(this.ctx,`dependencies.outdated`,this.internal.slug,()=>K(t,{ecosystem:e?.ecosystem,semver:e?.semver,releaseLatency:e?.releaseLatency,cache:e?.noCache?!1:void 0}))}};config={retrieve:()=>Promise.resolve(this.internal.activeWorktree.config)}};export{O as a,b as c,x as d,I as i,S as l,$ as n,D as o,Q as r,E as s,ae as t,C as u};
|