@owlmeans/viable-sdk 0.1.18-rc.1
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/README.md +128 -0
- package/build/api/index.d.ts +2 -0
- package/build/api/index.d.ts.map +1 -0
- package/build/api/index.js +2 -0
- package/build/api/index.js.map +1 -0
- package/build/api/remote.d.ts +14 -0
- package/build/api/remote.d.ts.map +1 -0
- package/build/api/remote.js +123 -0
- package/build/api/remote.js.map +1 -0
- package/build/consts.d.ts +47 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +47 -0
- package/build/consts.js.map +1 -0
- package/build/context/index.d.ts +22 -0
- package/build/context/index.d.ts.map +1 -0
- package/build/context/index.js +92 -0
- package/build/context/index.js.map +1 -0
- package/build/errors.d.ts +21 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +36 -0
- package/build/errors.js.map +1 -0
- package/build/executor/boot-check.d.ts +73 -0
- package/build/executor/boot-check.d.ts.map +1 -0
- package/build/executor/boot-check.js +155 -0
- package/build/executor/boot-check.js.map +1 -0
- package/build/executor/env.d.ts +25 -0
- package/build/executor/env.d.ts.map +1 -0
- package/build/executor/env.js +77 -0
- package/build/executor/env.js.map +1 -0
- package/build/executor/errors.d.ts +69 -0
- package/build/executor/errors.d.ts.map +1 -0
- package/build/executor/errors.js +80 -0
- package/build/executor/errors.js.map +1 -0
- package/build/executor/files.d.ts +124 -0
- package/build/executor/files.d.ts.map +1 -0
- package/build/executor/files.js +529 -0
- package/build/executor/files.js.map +1 -0
- package/build/executor/git.d.ts +52 -0
- package/build/executor/git.d.ts.map +1 -0
- package/build/executor/git.js +353 -0
- package/build/executor/git.js.map +1 -0
- package/build/executor/health.d.ts +92 -0
- package/build/executor/health.d.ts.map +1 -0
- package/build/executor/health.js +98 -0
- package/build/executor/health.js.map +1 -0
- package/build/executor/index.d.ts +39 -0
- package/build/executor/index.d.ts.map +1 -0
- package/build/executor/index.js +173 -0
- package/build/executor/index.js.map +1 -0
- package/build/executor/integrity.d.ts +17 -0
- package/build/executor/integrity.d.ts.map +1 -0
- package/build/executor/integrity.js +59 -0
- package/build/executor/integrity.js.map +1 -0
- package/build/executor/layout.d.ts +61 -0
- package/build/executor/layout.d.ts.map +1 -0
- package/build/executor/layout.js +98 -0
- package/build/executor/layout.js.map +1 -0
- package/build/executor/shell.d.ts +89 -0
- package/build/executor/shell.d.ts.map +1 -0
- package/build/executor/shell.js +280 -0
- package/build/executor/shell.js.map +1 -0
- package/build/executor/spawn.d.ts +111 -0
- package/build/executor/spawn.d.ts.map +1 -0
- package/build/executor/spawn.js +314 -0
- package/build/executor/spawn.js.map +1 -0
- package/build/harness/index.d.ts +28 -0
- package/build/harness/index.d.ts.map +1 -0
- package/build/harness/index.js +83 -0
- package/build/harness/index.js.map +1 -0
- package/build/harness/templates.d.ts +30 -0
- package/build/harness/templates.d.ts.map +1 -0
- package/build/harness/templates.js +162 -0
- package/build/harness/templates.js.map +1 -0
- package/build/index.d.ts +13 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +12 -0
- package/build/index.js.map +1 -0
- package/build/project/env.d.ts +108 -0
- package/build/project/env.d.ts.map +1 -0
- package/build/project/env.js +233 -0
- package/build/project/env.js.map +1 -0
- package/build/project/marker.d.ts +34 -0
- package/build/project/marker.d.ts.map +1 -0
- package/build/project/marker.js +54 -0
- package/build/project/marker.js.map +1 -0
- package/build/project/setup.d.ts +62 -0
- package/build/project/setup.d.ts.map +1 -0
- package/build/project/setup.js +190 -0
- package/build/project/setup.js.map +1 -0
- package/build/run/index.d.ts +75 -0
- package/build/run/index.d.ts.map +1 -0
- package/build/run/index.js +150 -0
- package/build/run/index.js.map +1 -0
- package/build/run/serve.d.ts +18 -0
- package/build/run/serve.d.ts.map +1 -0
- package/build/run/serve.js +148 -0
- package/build/run/serve.js.map +1 -0
- package/build/run/state.d.ts +75 -0
- package/build/run/state.d.ts.map +1 -0
- package/build/run/state.js +136 -0
- package/build/run/state.js.map +1 -0
- package/build/session/index.d.ts +17 -0
- package/build/session/index.d.ts.map +1 -0
- package/build/session/index.js +226 -0
- package/build/session/index.js.map +1 -0
- package/build/session/questions.d.ts +16 -0
- package/build/session/questions.d.ts.map +1 -0
- package/build/session/questions.js +17 -0
- package/build/session/questions.js.map +1 -0
- package/build/session/queue.d.ts +54 -0
- package/build/session/queue.d.ts.map +1 -0
- package/build/session/queue.js +102 -0
- package/build/session/queue.js.map +1 -0
- package/build/session/tasks.d.ts +15 -0
- package/build/session/tasks.d.ts.map +1 -0
- package/build/session/tasks.js +16 -0
- package/build/session/tasks.js.map +1 -0
- package/build/task/driver.d.ts +30 -0
- package/build/task/driver.d.ts.map +1 -0
- package/build/task/driver.js +120 -0
- package/build/task/driver.js.map +1 -0
- package/build/task/envelope.d.ts +35 -0
- package/build/task/envelope.d.ts.map +1 -0
- package/build/task/envelope.js +230 -0
- package/build/task/envelope.js.map +1 -0
- package/build/task/index.d.ts +4 -0
- package/build/task/index.d.ts.map +1 -0
- package/build/task/index.js +4 -0
- package/build/task/index.js.map +1 -0
- package/build/task/inquiry.d.ts +53 -0
- package/build/task/inquiry.d.ts.map +1 -0
- package/build/task/inquiry.js +232 -0
- package/build/task/inquiry.js.map +1 -0
- package/build/tools/catalogue.d.ts +17 -0
- package/build/tools/catalogue.d.ts.map +1 -0
- package/build/tools/catalogue.js +1169 -0
- package/build/tools/catalogue.js.map +1 -0
- package/build/tools/index.d.ts +7 -0
- package/build/tools/index.d.ts.map +1 -0
- package/build/tools/index.js +7 -0
- package/build/tools/index.js.map +1 -0
- package/build/tools/jobs.d.ts +12 -0
- package/build/tools/jobs.d.ts.map +1 -0
- package/build/tools/jobs.js +60 -0
- package/build/tools/jobs.js.map +1 -0
- package/build/tools/mcp.d.ts +43 -0
- package/build/tools/mcp.d.ts.map +1 -0
- package/build/tools/mcp.js +131 -0
- package/build/tools/mcp.js.map +1 -0
- package/build/tools/platform.d.ts +77 -0
- package/build/tools/platform.d.ts.map +1 -0
- package/build/tools/platform.js +316 -0
- package/build/tools/platform.js.map +1 -0
- package/build/tools/refusal.d.ts +66 -0
- package/build/tools/refusal.d.ts.map +1 -0
- package/build/tools/refusal.js +377 -0
- package/build/tools/refusal.js.map +1 -0
- package/build/tools/types.d.ts +83 -0
- package/build/tools/types.d.ts.map +1 -0
- package/build/tools/types.js +38 -0
- package/build/tools/types.js.map +1 -0
- package/build/types.d.ts +174 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/package.json +105 -0
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './consts.js';
|
|
2
|
+
export * from './errors.js';
|
|
3
|
+
export * from './project/marker.js';
|
|
4
|
+
export * from './project/env.js';
|
|
5
|
+
export * from './project/setup.js';
|
|
6
|
+
export * from './context/index.js';
|
|
7
|
+
export * from './api/index.js';
|
|
8
|
+
export * from './session/index.js';
|
|
9
|
+
export * from './task/index.js';
|
|
10
|
+
export * from './harness/index.js';
|
|
11
|
+
export * from './tools/index.js';
|
|
12
|
+
export type * from './types.js';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,mBAAmB,YAAY,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './consts.js';
|
|
2
|
+
export * from './errors.js';
|
|
3
|
+
export * from './project/marker.js';
|
|
4
|
+
export * from './project/env.js';
|
|
5
|
+
export * from './project/setup.js';
|
|
6
|
+
export * from './context/index.js';
|
|
7
|
+
export * from './api/index.js';
|
|
8
|
+
export * from './session/index.js';
|
|
9
|
+
export * from './task/index.js';
|
|
10
|
+
export * from './harness/index.js';
|
|
11
|
+
export * from './tools/index.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { ConfigurePayload, ConfigureResult, ConnectServices } from '@owlmeans/viable-common';
|
|
2
|
+
/**
|
|
3
|
+
* The target's `.env` files — the one thing about a local project the platform does not own.
|
|
4
|
+
*
|
|
5
|
+
* A slot's configuration is pushed into a pod and the pod's environment is the platform's to
|
|
6
|
+
* compose. On a developer's machine the same values live in a file the developer also writes: the
|
|
7
|
+
* database URL is theirs (nothing provisions a database on a laptop), and so is anything else
|
|
8
|
+
* they added. So a configure push rewrites only what it wrote last time — the block between the
|
|
9
|
+
* two markers — and every line outside it survives untouched.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Keys the target cannot authenticate without.
|
|
13
|
+
*
|
|
14
|
+
* Reported exactly like the publisher's `ConfigureAck.missing`: a key with an EMPTY value counts
|
|
15
|
+
* as MISSING, because a push that ran before a secret existed leaves one behind, and a target
|
|
16
|
+
* holding `OIDC_CLIENT=''` is a target whose users can never sign in — with nothing anywhere
|
|
17
|
+
* saying so.
|
|
18
|
+
*/
|
|
19
|
+
export declare const REQUIRED_ENV_KEYS: string[];
|
|
20
|
+
export interface TargetEnv {
|
|
21
|
+
/** Project-relative path → the values that file declares. */
|
|
22
|
+
files: Record<string, Record<string, string>>;
|
|
23
|
+
/** Everything the target's processes see, later files overriding earlier ones. */
|
|
24
|
+
values: Record<string, string>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parse a `.env` body.
|
|
28
|
+
*
|
|
29
|
+
* Deliberately small: `KEY=value`, an optional `export ` prefix, `#` comments, and one level of
|
|
30
|
+
* quoting. A dotenv library would add a dependency for a format the platform itself writes, and
|
|
31
|
+
* anything it understands that this does not is something the target's own runtime would have to
|
|
32
|
+
* understand too.
|
|
33
|
+
*/
|
|
34
|
+
export declare const parseEnv: (content: string) => Record<string, string>;
|
|
35
|
+
/**
|
|
36
|
+
* The api's and the worker's environment — the process side of the target.
|
|
37
|
+
*
|
|
38
|
+
* Everything a server may hold lives here and nowhere else: the database URL, the queue URL, the
|
|
39
|
+
* OIDC secret.
|
|
40
|
+
*/
|
|
41
|
+
export declare const ROOT_ENV_FILE = ".env";
|
|
42
|
+
/**
|
|
43
|
+
* The browser bundle's environment.
|
|
44
|
+
*
|
|
45
|
+
* A SEPARATE file, and the separation is the whole leak boundary: whatever is in it gets compiled
|
|
46
|
+
* into a bundle anybody can read, so nothing may reach it by merging in the server's file.
|
|
47
|
+
*/
|
|
48
|
+
export declare const webEnvFile: (dir: string) => string;
|
|
49
|
+
/** The env files a target has, in the order a later one overrides an earlier one. */
|
|
50
|
+
export declare const envFilePaths: (dir: string) => string[];
|
|
51
|
+
/** One env file's values; `{}` for a file that is not there. */
|
|
52
|
+
export declare const readEnvFile: (dir: string, file: string) => Promise<Record<string, string>>;
|
|
53
|
+
/** Every env file the target has, parsed. */
|
|
54
|
+
export declare const readEnv: (dir: string) => Promise<TargetEnv>;
|
|
55
|
+
/**
|
|
56
|
+
* Write the platform's block into each named file, keeping every line the user wrote.
|
|
57
|
+
*
|
|
58
|
+
* The block is delimited rather than diffed because the two writers have no other way to agree on
|
|
59
|
+
* ownership: the platform composes its half whole (it is the only side that knows the OIDC client
|
|
60
|
+
* and its secret) and the user's half is arbitrary. A file with no block yet gets one appended; a
|
|
61
|
+
* file that does not exist is created with the block as its whole content.
|
|
62
|
+
*/
|
|
63
|
+
export declare const writeEnv: (dir: string, payload: ConfigurePayload) => Promise<ConfigureResult>;
|
|
64
|
+
/**
|
|
65
|
+
* The same verdict a configure push answers with, without writing anything.
|
|
66
|
+
*
|
|
67
|
+
* A connector needs it before it asks the platform for work — a run that needs a database and a
|
|
68
|
+
* target that has none is a job that will block, and saying so up front is cheaper than finding
|
|
69
|
+
* out from a boot check.
|
|
70
|
+
*/
|
|
71
|
+
export declare const envStatus: (dir: string, probe?: Array<keyof ConnectServices>) => Promise<ConfigureResult>;
|
|
72
|
+
/** The file with the platform's block cut out — everything the user wrote, and only that. */
|
|
73
|
+
export declare const outsideManagedBlock: (existing: string) => string;
|
|
74
|
+
/**
|
|
75
|
+
* Stand down from every key the user has assigned themselves.
|
|
76
|
+
*
|
|
77
|
+
* "Everything outside the block is yours" has to be true of the VALUES, not only of the lines. A
|
|
78
|
+
* `.env` gives the last assignment of a key, and the block is appended, so a platform line silently
|
|
79
|
+
* outranked whatever the user wrote above it — `DATABASE_URL` included, which is the one value the
|
|
80
|
+
* platform cannot supply and the setup guide tells people to write themselves. The result was an
|
|
81
|
+
* application connecting to the placeholder no matter what its owner put in the file.
|
|
82
|
+
*
|
|
83
|
+
* A yielded key is replaced by a comment rather than dropped, so the file says why the platform is
|
|
84
|
+
* not setting something it normally would.
|
|
85
|
+
*/
|
|
86
|
+
export declare const yieldToUser: (existing: string, content: string) => string;
|
|
87
|
+
/**
|
|
88
|
+
* Replace what sits between the markers, and nothing else.
|
|
89
|
+
*
|
|
90
|
+
* A BEGIN with no END — a file truncated mid-write, or hand-edited — is rewritten to the end of
|
|
91
|
+
* the file rather than left alone: the alternative is a block that grows a second copy of itself
|
|
92
|
+
* on every push, and the text after a lost END was the platform's anyway.
|
|
93
|
+
*/
|
|
94
|
+
export declare const replaceManagedBlock: (existing: string, content: string) => string;
|
|
95
|
+
/**
|
|
96
|
+
* The host and port a connection URL names.
|
|
97
|
+
*
|
|
98
|
+
* `null` for anything unparseable, so a malformed value degrades to "not reachable" rather than
|
|
99
|
+
* throwing somewhere a caller has no branch for it.
|
|
100
|
+
*/
|
|
101
|
+
export declare const serviceEndpoint: (url: string | undefined) => {
|
|
102
|
+
host: string;
|
|
103
|
+
port: number;
|
|
104
|
+
} | null;
|
|
105
|
+
/** Whether something is listening where a connection string points. Exported for the setup flow. */
|
|
106
|
+
export declare const probeUrl: (url: string | undefined) => Promise<boolean>;
|
|
107
|
+
export declare const probePort: (host: string, port: number, timeoutMs?: number) => Promise<boolean>;
|
|
108
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/project/env.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAKjG;;;;;;;;GAQG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,UAAoD,CAAA;AAOlF,MAAM,WAAW,SAAS;IACxB,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAC7C,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC/B;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,YAAa,MAAM,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAkB/D,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,SAAS,CAAA;AAEnC;;;;;GAKG;AACH,eAAO,MAAM,UAAU,QAAS,MAAM,KAAG,MACyC,CAAA;AAElF,qFAAqF;AACrF,eAAO,MAAM,YAAY,QAAS,MAAM,KAAG,MAAM,EAAsC,CAAA;AAEvF,gEAAgE;AAChE,eAAO,MAAM,WAAW,QAAe,MAAM,QAAQ,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAI3F,CAAA;AAED,6CAA6C;AAC7C,eAAO,MAAM,OAAO,QAAe,MAAM,KAAG,OAAO,CAAC,SAAS,CAU5D,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,QAAe,MAAM,WAAW,gBAAgB,KAAG,OAAO,CAAC,eAAe,CAqB9F,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,QACf,MAAM,UAAS,KAAK,CAAC,MAAM,eAAe,CAAC,KAC/C,OAAO,CAAC,eAAe,CAQzB,CAAA;AAED,6FAA6F;AAC7F,eAAO,MAAM,mBAAmB,aAAc,MAAM,KAAG,MAStD,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,aAAc,MAAM,WAAW,MAAM,KAAG,MAU/D,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,aAAc,MAAM,WAAW,MAAM,KAAG,MAevE,CAAA;AAoBD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QAAS,MAAM,GAAG,SAAS,KAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAc1F,CAAA;AAED,oGAAoG;AACpG,eAAO,MAAM,QAAQ,QAAe,MAAM,GAAG,SAAS,KAAG,OAAO,CAAC,OAAO,CAIvE,CAAA;AAED,eAAO,MAAM,SAAS,SACd,MAAM,QAAQ,MAAM,yBACzB,OAAO,CAAC,OAAO,CAWhB,CAAA"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
import p from 'node:path';
|
|
4
|
+
import { CONNECT_ENV_BEGIN, CONNECT_ENV_END, SubProject } from '@owlmeans/viable-common';
|
|
5
|
+
import { confineToProject } from '../executor/files.js';
|
|
6
|
+
import { subprojectDir, targetPaths } from '../executor/layout.js';
|
|
7
|
+
/**
|
|
8
|
+
* The target's `.env` files — the one thing about a local project the platform does not own.
|
|
9
|
+
*
|
|
10
|
+
* A slot's configuration is pushed into a pod and the pod's environment is the platform's to
|
|
11
|
+
* compose. On a developer's machine the same values live in a file the developer also writes: the
|
|
12
|
+
* database URL is theirs (nothing provisions a database on a laptop), and so is anything else
|
|
13
|
+
* they added. So a configure push rewrites only what it wrote last time — the block between the
|
|
14
|
+
* two markers — and every line outside it survives untouched.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Keys the target cannot authenticate without.
|
|
18
|
+
*
|
|
19
|
+
* Reported exactly like the publisher's `ConfigureAck.missing`: a key with an EMPTY value counts
|
|
20
|
+
* as MISSING, because a push that ran before a secret existed leaves one behind, and a target
|
|
21
|
+
* holding `OIDC_CLIENT=''` is a target whose users can never sign in — with nothing anywhere
|
|
22
|
+
* saying so.
|
|
23
|
+
*/
|
|
24
|
+
export const REQUIRED_ENV_KEYS = ['OIDC_ISSUER_URL', 'OIDC_CLIENT', 'OIDC_SECRET'];
|
|
25
|
+
/** How long a service probe waits before calling a port unreachable. */
|
|
26
|
+
const PROBE_TIMEOUT_MS = 1_500;
|
|
27
|
+
const DEFAULT_PORTS = { postgres: 5432, postgresql: 5432, redis: 6379, rediss: 6379, valkey: 6379 };
|
|
28
|
+
/**
|
|
29
|
+
* Parse a `.env` body.
|
|
30
|
+
*
|
|
31
|
+
* Deliberately small: `KEY=value`, an optional `export ` prefix, `#` comments, and one level of
|
|
32
|
+
* quoting. A dotenv library would add a dependency for a format the platform itself writes, and
|
|
33
|
+
* anything it understands that this does not is something the target's own runtime would have to
|
|
34
|
+
* understand too.
|
|
35
|
+
*/
|
|
36
|
+
export const parseEnv = (content) => {
|
|
37
|
+
const values = {};
|
|
38
|
+
for (const raw of content.split('\n')) {
|
|
39
|
+
const line = raw.trim();
|
|
40
|
+
if (line === '' || line.startsWith('#'))
|
|
41
|
+
continue;
|
|
42
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
|
|
43
|
+
if (match == null)
|
|
44
|
+
continue;
|
|
45
|
+
let value = match[2].trim();
|
|
46
|
+
if ((value.startsWith('"') && value.endsWith('"') && value.length > 1)
|
|
47
|
+
|| (value.startsWith("'") && value.endsWith("'") && value.length > 1)) {
|
|
48
|
+
value = value.slice(1, -1);
|
|
49
|
+
}
|
|
50
|
+
values[match[1]] = value;
|
|
51
|
+
}
|
|
52
|
+
return values;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* The api's and the worker's environment — the process side of the target.
|
|
56
|
+
*
|
|
57
|
+
* Everything a server may hold lives here and nowhere else: the database URL, the queue URL, the
|
|
58
|
+
* OIDC secret.
|
|
59
|
+
*/
|
|
60
|
+
export const ROOT_ENV_FILE = '.env';
|
|
61
|
+
/**
|
|
62
|
+
* The browser bundle's environment.
|
|
63
|
+
*
|
|
64
|
+
* A SEPARATE file, and the separation is the whole leak boundary: whatever is in it gets compiled
|
|
65
|
+
* into a bundle anybody can read, so nothing may reach it by merging in the server's file.
|
|
66
|
+
*/
|
|
67
|
+
export const webEnvFile = (dir) => `${targetPaths(dir).dir}/${subprojectDir(dir, SubProject.Web)}/${ROOT_ENV_FILE}`;
|
|
68
|
+
/** The env files a target has, in the order a later one overrides an earlier one. */
|
|
69
|
+
export const envFilePaths = (dir) => [ROOT_ENV_FILE, webEnvFile(dir)];
|
|
70
|
+
/** One env file's values; `{}` for a file that is not there. */
|
|
71
|
+
export const readEnvFile = async (dir, file) => {
|
|
72
|
+
const content = await fs.readFile(confineToProject(dir, file), 'utf-8').catch(() => null);
|
|
73
|
+
return content == null ? {} : parseEnv(content);
|
|
74
|
+
};
|
|
75
|
+
/** Every env file the target has, parsed. */
|
|
76
|
+
export const readEnv = async (dir) => {
|
|
77
|
+
const files = {};
|
|
78
|
+
let values = {};
|
|
79
|
+
for (const file of envFilePaths(dir)) {
|
|
80
|
+
const parsed = await readEnvFile(dir, file);
|
|
81
|
+
files[file] = parsed;
|
|
82
|
+
values = { ...values, ...parsed };
|
|
83
|
+
}
|
|
84
|
+
return { files, values };
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Write the platform's block into each named file, keeping every line the user wrote.
|
|
88
|
+
*
|
|
89
|
+
* The block is delimited rather than diffed because the two writers have no other way to agree on
|
|
90
|
+
* ownership: the platform composes its half whole (it is the only side that knows the OIDC client
|
|
91
|
+
* and its secret) and the user's half is arbitrary. A file with no block yet gets one appended; a
|
|
92
|
+
* file that does not exist is created with the block as its whole content.
|
|
93
|
+
*/
|
|
94
|
+
export const writeEnv = async (dir, payload) => {
|
|
95
|
+
const written = [];
|
|
96
|
+
for (const file of payload.files ?? []) {
|
|
97
|
+
const path = confineToProject(dir, file.path);
|
|
98
|
+
const existing = await fs.readFile(path, 'utf-8').catch(() => '');
|
|
99
|
+
const next = replaceManagedBlock(existing, yieldToUser(existing, file.content ?? ''));
|
|
100
|
+
if (next !== existing) {
|
|
101
|
+
await fs.ensureDir(p.dirname(path));
|
|
102
|
+
await fs.writeFile(path, next);
|
|
103
|
+
}
|
|
104
|
+
written.push(file.path);
|
|
105
|
+
}
|
|
106
|
+
const { values } = await readEnv(dir);
|
|
107
|
+
return {
|
|
108
|
+
written,
|
|
109
|
+
missing: REQUIRED_ENV_KEYS.filter(key => (values[key] ?? '') === ''),
|
|
110
|
+
services: await probeServices(values, payload.probe),
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* The same verdict a configure push answers with, without writing anything.
|
|
115
|
+
*
|
|
116
|
+
* A connector needs it before it asks the platform for work — a run that needs a database and a
|
|
117
|
+
* target that has none is a job that will block, and saying so up front is cheaper than finding
|
|
118
|
+
* out from a boot check.
|
|
119
|
+
*/
|
|
120
|
+
export const envStatus = async (dir, probe = ['db', 'valkey']) => {
|
|
121
|
+
const { values } = await readEnv(dir);
|
|
122
|
+
return {
|
|
123
|
+
written: [],
|
|
124
|
+
missing: REQUIRED_ENV_KEYS.filter(key => (values[key] ?? '') === ''),
|
|
125
|
+
services: await probeServices(values, probe),
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
/** The file with the platform's block cut out — everything the user wrote, and only that. */
|
|
129
|
+
export const outsideManagedBlock = (existing) => {
|
|
130
|
+
const begin = existing.indexOf(CONNECT_ENV_BEGIN);
|
|
131
|
+
if (begin < 0)
|
|
132
|
+
return existing;
|
|
133
|
+
const end = existing.indexOf(CONNECT_ENV_END, begin);
|
|
134
|
+
return end < 0
|
|
135
|
+
? existing.slice(0, begin)
|
|
136
|
+
: `${existing.slice(0, begin)}${existing.slice(end + CONNECT_ENV_END.length)}`;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Stand down from every key the user has assigned themselves.
|
|
140
|
+
*
|
|
141
|
+
* "Everything outside the block is yours" has to be true of the VALUES, not only of the lines. A
|
|
142
|
+
* `.env` gives the last assignment of a key, and the block is appended, so a platform line silently
|
|
143
|
+
* outranked whatever the user wrote above it — `DATABASE_URL` included, which is the one value the
|
|
144
|
+
* platform cannot supply and the setup guide tells people to write themselves. The result was an
|
|
145
|
+
* application connecting to the placeholder no matter what its owner put in the file.
|
|
146
|
+
*
|
|
147
|
+
* A yielded key is replaced by a comment rather than dropped, so the file says why the platform is
|
|
148
|
+
* not setting something it normally would.
|
|
149
|
+
*/
|
|
150
|
+
export const yieldToUser = (existing, content) => {
|
|
151
|
+
const theirs = parseEnv(outsideManagedBlock(existing));
|
|
152
|
+
if (Object.keys(theirs).length < 1)
|
|
153
|
+
return content;
|
|
154
|
+
return content.split('\n').map(raw => {
|
|
155
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(raw.trim());
|
|
156
|
+
if (match == null || !(match[1] in theirs))
|
|
157
|
+
return raw;
|
|
158
|
+
return `# ${match[1]} is set in your own lines, so this block leaves it to you.`;
|
|
159
|
+
}).join('\n');
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Replace what sits between the markers, and nothing else.
|
|
163
|
+
*
|
|
164
|
+
* A BEGIN with no END — a file truncated mid-write, or hand-edited — is rewritten to the end of
|
|
165
|
+
* the file rather than left alone: the alternative is a block that grows a second copy of itself
|
|
166
|
+
* on every push, and the text after a lost END was the platform's anyway.
|
|
167
|
+
*/
|
|
168
|
+
export const replaceManagedBlock = (existing, content) => {
|
|
169
|
+
const body = content.replace(/^\n+|\n+$/g, '');
|
|
170
|
+
const block = `${CONNECT_ENV_BEGIN}\n${body === '' ? '' : `${body}\n`}${CONNECT_ENV_END}\n`;
|
|
171
|
+
const begin = existing.indexOf(CONNECT_ENV_BEGIN);
|
|
172
|
+
if (begin < 0) {
|
|
173
|
+
const prefix = existing === '' || existing.endsWith('\n') ? existing : `${existing}\n`;
|
|
174
|
+
return `${prefix}${block}`;
|
|
175
|
+
}
|
|
176
|
+
const end = existing.indexOf(CONNECT_ENV_END, begin);
|
|
177
|
+
const after = end < 0 ? existing.length : end + CONNECT_ENV_END.length;
|
|
178
|
+
return `${existing.slice(0, begin)}${block}${existing.slice(after).replace(/^\n/, '')}`;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* What this machine actually provides.
|
|
182
|
+
*
|
|
183
|
+
* Reported, never assumed — the platform provisions nothing here, so the only honest answer comes
|
|
184
|
+
* from a connect attempt. A service the caller did not ask about stays `false` rather than
|
|
185
|
+
* becoming an unrequested round trip.
|
|
186
|
+
*/
|
|
187
|
+
const probeServices = async (values, probe) => {
|
|
188
|
+
const wanted = new Set(probe ?? []);
|
|
189
|
+
return {
|
|
190
|
+
db: wanted.has('db') ? await probeUrl(values.DATABASE_URL) : false,
|
|
191
|
+
valkey: wanted.has('valkey') ? await probeUrl(values.VALKEY_URL) : false,
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* The host and port a connection URL names.
|
|
196
|
+
*
|
|
197
|
+
* `null` for anything unparseable, so a malformed value degrades to "not reachable" rather than
|
|
198
|
+
* throwing somewhere a caller has no branch for it.
|
|
199
|
+
*/
|
|
200
|
+
export const serviceEndpoint = (url) => {
|
|
201
|
+
if (url == null || url === '')
|
|
202
|
+
return null;
|
|
203
|
+
try {
|
|
204
|
+
const parsed = new URL(url);
|
|
205
|
+
const port = parsed.port !== ''
|
|
206
|
+
? parseInt(parsed.port)
|
|
207
|
+
: DEFAULT_PORTS[parsed.protocol.replace(':', '')];
|
|
208
|
+
if (parsed.hostname === '' || port == null || Number.isNaN(port))
|
|
209
|
+
return null;
|
|
210
|
+
return { host: parsed.hostname, port };
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
/** Whether something is listening where a connection string points. Exported for the setup flow. */
|
|
217
|
+
export const probeUrl = async (url) => {
|
|
218
|
+
const endpoint = serviceEndpoint(url);
|
|
219
|
+
return endpoint == null ? false : await probePort(endpoint.host, endpoint.port);
|
|
220
|
+
};
|
|
221
|
+
export const probePort = async (host, port, timeoutMs = PROBE_TIMEOUT_MS) => await new Promise(resolve => {
|
|
222
|
+
const socket = net.connect({ host, port });
|
|
223
|
+
const done = (value) => {
|
|
224
|
+
socket.removeAllListeners();
|
|
225
|
+
socket.destroy();
|
|
226
|
+
resolve(value);
|
|
227
|
+
};
|
|
228
|
+
socket.setTimeout(timeoutMs);
|
|
229
|
+
socket.once('connect', () => done(true));
|
|
230
|
+
socket.once('timeout', () => done(false));
|
|
231
|
+
socket.once('error', () => done(false));
|
|
232
|
+
});
|
|
233
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/project/env.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ConnectMarker } from '@owlmeans/viable-common';
|
|
2
|
+
/**
|
|
3
|
+
* What a local project keeps so a later session knows which platform project it is.
|
|
4
|
+
*
|
|
5
|
+
* No secrets: an id, a slug and the API it belongs to. A connector attaching to a directory has
|
|
6
|
+
* nothing else to go on — the tree itself is a generated application like any other, and asking
|
|
7
|
+
* the platform "which of my projects is this" needs an id the tree already carries.
|
|
8
|
+
*/
|
|
9
|
+
export interface DiscoveredProject {
|
|
10
|
+
/** The directory holding `.viable/connect.json` — the project root, not where the search began. */
|
|
11
|
+
dir: string;
|
|
12
|
+
marker: ConnectMarker;
|
|
13
|
+
}
|
|
14
|
+
/** The marker this directory carries, or `null` when it carries none or an unreadable one. */
|
|
15
|
+
export declare const readMarker: (dir: string) => Promise<ConnectMarker | null>;
|
|
16
|
+
export declare const writeMarker: (dir: string, marker: ConnectMarker) => Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Find the project a directory belongs to, by walking up.
|
|
19
|
+
*
|
|
20
|
+
* An agent is started wherever its user happened to be — usually several levels inside the tree —
|
|
21
|
+
* and every tool that acts on "this project" has to mean the same one whichever of those
|
|
22
|
+
* directories it was asked from. The walk stops at the filesystem root; a machine with no marker
|
|
23
|
+
* anywhere above answers `null`, which is how a connector knows to offer attaching rather than
|
|
24
|
+
* assuming.
|
|
25
|
+
*/
|
|
26
|
+
export declare const discoverProject: (startDir: string) => Promise<DiscoveredProject | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Whether this directory holds the generated application.
|
|
29
|
+
*
|
|
30
|
+
* Asked before a connector offers to drive a directory, so a person who pointed it at the wrong
|
|
31
|
+
* folder learns that from a sentence rather than from a build that refuses everything.
|
|
32
|
+
*/
|
|
33
|
+
export declare const isViableTree: (dir: string) => Promise<boolean>;
|
|
34
|
+
//# sourceMappingURL=marker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marker.d.ts","sourceRoot":"","sources":["../../src/project/marker.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAI5D;;;;;;GAMG;AAEH,MAAM,WAAW,iBAAiB;IAChC,mGAAmG;IACnG,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,aAAa,CAAA;CACtB;AAED,8FAA8F;AAC9F,eAAO,MAAM,UAAU,QAAe,MAAM,KAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAa1E,CAAA;AAED,eAAO,MAAM,WAAW,QAAe,MAAM,UAAU,aAAa,KAAG,OAAO,CAAC,IAAI,CAKlF,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,aAAoB,MAAM,KAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAcxF,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,QAAe,MAAM,KAAG,OAAO,CAAC,OAAO,CAClC,CAAA"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import p from 'node:path';
|
|
3
|
+
import { CONNECT_MARKER_DIR, CONNECT_MARKER_FILE } from '@owlmeans/viable-common';
|
|
4
|
+
import { verifyTarget } from '../executor/integrity.js';
|
|
5
|
+
/** The marker this directory carries, or `null` when it carries none or an unreadable one. */
|
|
6
|
+
export const readMarker = async (dir) => {
|
|
7
|
+
const content = await fs.readFile(p.join(dir, CONNECT_MARKER_FILE), 'utf-8').catch(() => null);
|
|
8
|
+
if (content == null)
|
|
9
|
+
return null;
|
|
10
|
+
try {
|
|
11
|
+
const parsed = JSON.parse(content);
|
|
12
|
+
return typeof parsed === 'object' && parsed !== null ? parsed : null;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// A half-written or hand-edited marker is the same as none: the connector re-attaches by
|
|
16
|
+
// slug and writes a fresh one, which is strictly better than refusing to open the project.
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export const writeMarker = async (dir, marker) => {
|
|
21
|
+
await fs.ensureDir(p.join(dir, CONNECT_MARKER_DIR));
|
|
22
|
+
await fs.writeFile(p.join(dir, CONNECT_MARKER_FILE), `${JSON.stringify(marker, null, 2)}\n`);
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Find the project a directory belongs to, by walking up.
|
|
26
|
+
*
|
|
27
|
+
* An agent is started wherever its user happened to be — usually several levels inside the tree —
|
|
28
|
+
* and every tool that acts on "this project" has to mean the same one whichever of those
|
|
29
|
+
* directories it was asked from. The walk stops at the filesystem root; a machine with no marker
|
|
30
|
+
* anywhere above answers `null`, which is how a connector knows to offer attaching rather than
|
|
31
|
+
* assuming.
|
|
32
|
+
*/
|
|
33
|
+
export const discoverProject = async (startDir) => {
|
|
34
|
+
let dir = p.resolve(startDir);
|
|
35
|
+
for (;;) {
|
|
36
|
+
const marker = await readMarker(dir);
|
|
37
|
+
if (marker != null) {
|
|
38
|
+
return { dir, marker };
|
|
39
|
+
}
|
|
40
|
+
const parent = p.dirname(dir);
|
|
41
|
+
if (parent === dir) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
dir = parent;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Whether this directory holds the generated application.
|
|
49
|
+
*
|
|
50
|
+
* Asked before a connector offers to drive a directory, so a person who pointed it at the wrong
|
|
51
|
+
* folder learns that from a sentence rather than from a build that refuses everything.
|
|
52
|
+
*/
|
|
53
|
+
export const isViableTree = async (dir) => (await verifyTarget(dir)).ok;
|
|
54
|
+
//# sourceMappingURL=marker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marker.js","sourceRoot":"","sources":["../../src/project/marker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAA;AACzB,OAAO,CAAC,MAAM,WAAW,CAAA;AAEzB,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAGjF,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAgBvD,8FAA8F;AAC9F,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAAE,GAAW,EAAiC,EAAE;IAC7E,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,mBAAmB,CAAC,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;IAC9F,IAAI,OAAO,IAAI,IAAI;QAAE,OAAO,IAAI,CAAA;IAEhC,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAE3C,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,MAAuB,CAAC,CAAC,CAAC,IAAI,CAAA;IACvF,CAAC;IAAC,MAAM,CAAC;QACP,yFAAyF;QACzF,2FAA2F;QAC3F,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,GAAW,EAAE,MAAqB,EAAiB,EAAE;IACrF,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC,CAAA;IACnD,MAAM,EAAE,CAAC,SAAS,CAChB,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,mBAAmB,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CACzE,CAAA;AACH,CAAC,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,QAAgB,EAAqC,EAAE;IAC3F,IAAI,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC7B,SAAS,CAAC;QACR,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAA;QACpC,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;YACnB,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAA;QACxB,CAAC;QAED,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAC7B,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YACnB,OAAO,IAAI,CAAA;QACb,CAAC;QACD,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;AACH,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,GAAW,EAAoB,EAAE,CAClE,CAAC,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a project needs before it can run here, and what this machine already provides.
|
|
3
|
+
*
|
|
4
|
+
* The platform provisions nothing on somebody's own computer: a database and, for a project with a
|
|
5
|
+
* background worker, a queue store are the two things it cannot supply and cannot guess. So the
|
|
6
|
+
* connector's job is to find out what is already there, and where nothing is, to ASK — never to
|
|
7
|
+
* install something on a person's machine on its own initiative, and never to invent a connection
|
|
8
|
+
* string and let the failure surface later as a boot error nobody can place.
|
|
9
|
+
*/
|
|
10
|
+
export interface ServiceState {
|
|
11
|
+
/** A connection string is present in the project's own half of the `.env`. */
|
|
12
|
+
configured: boolean;
|
|
13
|
+
/** Something answers where it points. False for an unset URL and for one nothing listens on. */
|
|
14
|
+
reachable: boolean;
|
|
15
|
+
/** The value, with everything between the scheme and the host removed. */
|
|
16
|
+
redacted?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface SetupReport {
|
|
19
|
+
dir: string;
|
|
20
|
+
/** Only a project with a worker needs a queue store; for the rest it is noise. */
|
|
21
|
+
needsWorker: boolean;
|
|
22
|
+
database: ServiceState;
|
|
23
|
+
queue: ServiceState;
|
|
24
|
+
/** The command-line tools the paths below depend on. */
|
|
25
|
+
tools: {
|
|
26
|
+
bun: boolean;
|
|
27
|
+
git: boolean;
|
|
28
|
+
docker: boolean;
|
|
29
|
+
};
|
|
30
|
+
platform: NodeJS.Platform;
|
|
31
|
+
/** Whether the file holding these values is kept out of the project's history. */
|
|
32
|
+
envIgnored: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** Everything a connection string says except who is connecting. */
|
|
35
|
+
export declare const redactUrl: (url: string) => string;
|
|
36
|
+
/** Whether a command can be run here. `--version` rather than `which`, which Windows lacks. */
|
|
37
|
+
export declare const probeCommand: (command: string) => Promise<boolean>;
|
|
38
|
+
export declare const readSetupReport: (dir: string) => Promise<SetupReport>;
|
|
39
|
+
/** What is still missing before the application can start. */
|
|
40
|
+
export declare const missingServices: (report: SetupReport) => Array<'database' | 'queue'>;
|
|
41
|
+
/**
|
|
42
|
+
* The answer a parent agent reads when the project cannot run yet.
|
|
43
|
+
*
|
|
44
|
+
* It is written as a QUESTION with three answers, because which one is right is the user's to
|
|
45
|
+
* decide and nobody else's: they may already run Postgres, they may want one installed here, or
|
|
46
|
+
* they may prefer a hosted free tier and never install anything. Guessing means either a container
|
|
47
|
+
* on somebody's machine they did not ask for, or a connection string invented for a database that
|
|
48
|
+
* does not exist — and the second surfaces minutes later as a boot error nobody can place.
|
|
49
|
+
*/
|
|
50
|
+
export declare const renderSetupGuide: (report: SetupReport) => string;
|
|
51
|
+
/**
|
|
52
|
+
* Write the user's own service lines into their half of the `.env`.
|
|
53
|
+
*
|
|
54
|
+
* Replaces the key where it already exists outside the managed block and appends it otherwise, so
|
|
55
|
+
* running this twice leaves one line rather than two — a `.env` takes the LAST assignment, and two
|
|
56
|
+
* copies of a credential is one of them being quietly ignored.
|
|
57
|
+
*/
|
|
58
|
+
export declare const setUserEnv: (dir: string, values: Record<string, string>) => Promise<{
|
|
59
|
+
written: string[];
|
|
60
|
+
file: string;
|
|
61
|
+
}>;
|
|
62
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/project/setup.ts"],"names":[],"mappings":"AASA;;;;;;;;GAQG;AAEH,MAAM,WAAW,YAAY;IAC3B,8EAA8E;IAC9E,UAAU,EAAE,OAAO,CAAA;IACnB,gGAAgG;IAChG,SAAS,EAAE,OAAO,CAAA;IAClB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,kFAAkF;IAClF,WAAW,EAAE,OAAO,CAAA;IACpB,QAAQ,EAAE,YAAY,CAAA;IACtB,KAAK,EAAE,YAAY,CAAA;IACnB,wDAAwD;IACxD,KAAK,EAAE;QAAE,GAAG,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAA;IACtD,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAA;IACzB,kFAAkF;IAClF,UAAU,EAAE,OAAO,CAAA;CACpB;AAED,oEAAoE;AACpE,eAAO,MAAM,SAAS,QAAS,MAAM,KAAG,MAYvC,CAAA;AAED,+FAA+F;AAC/F,eAAO,MAAM,YAAY,YAAmB,MAAM,KAAG,OAAO,CAAC,OAAO,CAOhE,CAAA;AAmBJ,eAAO,MAAM,eAAe,QAAe,MAAM,KAAG,OAAO,CAAC,WAAW,CAoBtE,CAAA;AAED,8DAA8D;AAC9D,eAAO,MAAM,eAAe,WAAY,WAAW,KAAG,KAAK,CAAC,UAAU,GAAG,OAAO,CAG/E,CAAA;AAmBD;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,WAAY,WAAW,KAAG,MAoFtD,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,QAChB,MAAM,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC1C,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CA+C7C,CAAA"}
|