@hyperfixation/cli 0.1.2 → 0.1.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/dist/cloud-steps/context.d.ts +2 -1
- package/dist/cloud-steps/context.js +2 -1
- package/dist/cloud-steps/coolify.d.ts +10 -0
- package/dist/cloud-steps/coolify.js +16 -7
- package/dist/cloud-steps/template.js +19 -5
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +1 -1
- package/dist/providers/cloudflare.js +1 -0
- package/dist/providers/coolify.d.ts +20 -1
- package/dist/providers/coolify.js +11 -0
- package/dist/providers/github.js +1 -0
- package/dist/providers/http.d.ts +23 -6
- package/dist/providers/http.js +46 -8
- package/dist/providers/langfuse.js +2 -0
- package/dist/providers/sentry.js +1 -0
- package/dist/state.d.ts +9 -0
- package/dist/state.js +1 -0
- package/package.json +6 -6
|
@@ -7,7 +7,8 @@ import type { FetchLike } from "../providers/http.js";
|
|
|
7
7
|
* A step refused to act, or a command it ran failed.
|
|
8
8
|
*
|
|
9
9
|
* Carries no provider body and no environment: the only thing a step is allowed to say about a
|
|
10
|
-
* secret is that it has one. `ProviderError` keeps the same rule for the HTTP half
|
|
10
|
+
* secret is that it has one. `ProviderError` keeps the same rule for the HTTP half — it quotes the
|
|
11
|
+
* provider's own explanation, with every credential and every value it was sent blanked out.
|
|
11
12
|
*/
|
|
12
13
|
export declare class StepFailed extends Error {
|
|
13
14
|
constructor(message: string);
|
|
@@ -9,7 +9,8 @@ import { fetchTemplate } from "../template-source.js";
|
|
|
9
9
|
* A step refused to act, or a command it ran failed.
|
|
10
10
|
*
|
|
11
11
|
* Carries no provider body and no environment: the only thing a step is allowed to say about a
|
|
12
|
-
* secret is that it has one. `ProviderError` keeps the same rule for the HTTP half
|
|
12
|
+
* secret is that it has one. `ProviderError` keeps the same rule for the HTTP half — it quotes the
|
|
13
|
+
* provider's own explanation, with every credential and every value it was sent blanked out.
|
|
13
14
|
*/
|
|
14
15
|
export class StepFailed extends Error {
|
|
15
16
|
constructor(message) {
|
|
@@ -6,6 +6,16 @@ import { StepFailed, type CloudStepContext } from "./context.js";
|
|
|
6
6
|
export declare const COOLIFY_ENVIRONMENT = "production";
|
|
7
7
|
/** Where the build pack looks for the compose file, relative to the repository root. */
|
|
8
8
|
export declare const COMPOSE_LOCATION = "/docker-compose.prod.yml";
|
|
9
|
+
/**
|
|
10
|
+
* The compose service the app's domain is attached to — the one the template publishes 3000 from.
|
|
11
|
+
*
|
|
12
|
+
* A `dockercompose` application cannot take `domains` at all (Coolify 4.3.21: 422 `The domains
|
|
13
|
+
* field cannot be used for dockercompose applications`), and `docker_compose_domains` names a
|
|
14
|
+
* service, so one of them has to be named here. A template that renamed `web` would deploy an app
|
|
15
|
+
* Coolify's proxy routes nothing to, which is why this is a constant with a test behind it rather
|
|
16
|
+
* than a literal in the payload.
|
|
17
|
+
*/
|
|
18
|
+
export declare const COMPOSE_DOMAIN_SERVICE = "web";
|
|
9
19
|
/** The compose file the drift assertion reads, under the app's directory. */
|
|
10
20
|
export declare const PROD_COMPOSE_FILE = "docker-compose.prod.yml";
|
|
11
21
|
/**
|
|
@@ -9,6 +9,16 @@ import { appFqdn, StepFailed } from "./context.js";
|
|
|
9
9
|
export const COOLIFY_ENVIRONMENT = "production";
|
|
10
10
|
/** Where the build pack looks for the compose file, relative to the repository root. */
|
|
11
11
|
export const COMPOSE_LOCATION = "/docker-compose.prod.yml";
|
|
12
|
+
/**
|
|
13
|
+
* The compose service the app's domain is attached to — the one the template publishes 3000 from.
|
|
14
|
+
*
|
|
15
|
+
* A `dockercompose` application cannot take `domains` at all (Coolify 4.3.21: 422 `The domains
|
|
16
|
+
* field cannot be used for dockercompose applications`), and `docker_compose_domains` names a
|
|
17
|
+
* service, so one of them has to be named here. A template that renamed `web` would deploy an app
|
|
18
|
+
* Coolify's proxy routes nothing to, which is why this is a constant with a test behind it rather
|
|
19
|
+
* than a literal in the payload.
|
|
20
|
+
*/
|
|
21
|
+
export const COMPOSE_DOMAIN_SERVICE = "web";
|
|
12
22
|
/** The compose file the drift assertion reads, under the app's directory. */
|
|
13
23
|
export const PROD_COMPOSE_FILE = "docker-compose.prod.yml";
|
|
14
24
|
/**
|
|
@@ -223,12 +233,11 @@ async function findOrCreateProject(context, coolify) {
|
|
|
223
233
|
async function productionEnvironment(context, coolify, projectUuid) {
|
|
224
234
|
const environments = await coolify.listEnvironments(projectUuid);
|
|
225
235
|
const production = environments.find((environment) => environment.name === COOLIFY_ENVIRONMENT);
|
|
226
|
-
if (production
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return production.uuid;
|
|
236
|
+
if (production !== undefined)
|
|
237
|
+
return production.uuid;
|
|
238
|
+
const created = await coolify.createEnvironment(projectUuid, { name: COOLIFY_ENVIRONMENT });
|
|
239
|
+
context.io.out(`${context.names.given}: created the ${COOLIFY_ENVIRONMENT} environment in the Coolify project`);
|
|
240
|
+
return created.uuid;
|
|
232
241
|
}
|
|
233
242
|
async function findOrCreateApplication(context, coolify, projectUuid, environmentUuid) {
|
|
234
243
|
const name = context.names.given;
|
|
@@ -255,7 +264,7 @@ async function findOrCreateApplication(context, coolify, projectUuid, environmen
|
|
|
255
264
|
docker_compose_location: COMPOSE_LOCATION,
|
|
256
265
|
connect_to_docker_network: true,
|
|
257
266
|
name,
|
|
258
|
-
|
|
267
|
+
docker_compose_domains: [{ name: COMPOSE_DOMAIN_SERVICE, domain: `https://${fqdn}` }],
|
|
259
268
|
// The `deploy` step is what deploys, once the environment is set and the database migrated.
|
|
260
269
|
instant_deploy: false,
|
|
261
270
|
});
|
|
@@ -37,19 +37,33 @@ export const templateStep = {
|
|
|
37
37
|
await substituteTree(fetched, names);
|
|
38
38
|
// The marker is what `assertTemplateSource` looks for: an app is never a template twice.
|
|
39
39
|
await rm(path.join(fetched, TEMPLATE_MARKER));
|
|
40
|
+
// Recorded before the rename, not after: a crash between the two must leave a directory this
|
|
41
|
+
// run's state vouches for, or the rerun would refuse the app it just made.
|
|
42
|
+
await context.state.patch({ templateStartedAt: new Date(context.now()).toISOString() });
|
|
40
43
|
await rename(fetched, dir);
|
|
41
44
|
context.io.out(`${names.given}: template fetched into ${dir}`);
|
|
42
45
|
},
|
|
43
46
|
};
|
|
44
47
|
/**
|
|
45
|
-
* A directory already at the target: this app on a run
|
|
48
|
+
* A directory already at the target: this app on a run that crashed after the rename, or
|
|
49
|
+
* something else.
|
|
46
50
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
51
|
+
* Adopted only on a genuine resume — the state cache records that this app's template step began
|
|
52
|
+
* (`templateStartedAt`) or finished. With no such record the directory is not ours however much it
|
|
53
|
+
* looks like it: an earlier `hf new --local` leaves a substituted scaffold with the right
|
|
54
|
+
* `package.json` name, and adopting it once pushed a stale template to a new repo.
|
|
55
|
+
*
|
|
56
|
+
* On a resume, "this app" means a substituted template — its `package.json` carries the
|
|
57
|
+
* underscored app name and the marker is gone. Anything else is the local flow's rule, refused
|
|
58
|
+
* rather than written into.
|
|
50
59
|
*/
|
|
51
60
|
async function adoptOrRefuse(context) {
|
|
52
|
-
const { dir, names } = context;
|
|
61
|
+
const { dir, names, state } = context;
|
|
62
|
+
const resuming = state.state.templateStartedAt !== undefined || state.isDone("template");
|
|
63
|
+
if (!resuming) {
|
|
64
|
+
throw new TemplateError(`${dir} already exists and hf has no record of creating it, so it will not adopt it on a ` +
|
|
65
|
+
`first run. Move it away (or delete it) and rerun; the template is fetched fresh.`);
|
|
66
|
+
}
|
|
53
67
|
const substituted = !(await exists(path.join(dir, TEMPLATE_MARKER))) && (await packageName(dir)) === names.appName;
|
|
54
68
|
if (!substituted) {
|
|
55
69
|
throw new TemplateError(`${dir} already exists; hf new will not write into it`);
|
package/dist/doctor.d.ts
CHANGED
|
@@ -49,7 +49,7 @@ export interface DoctorOptions {
|
|
|
49
49
|
* whole point of this command is one screen that says whether anything needs attention.
|
|
50
50
|
*
|
|
51
51
|
* Nothing here prints a secret. The read token authorizes the status request and never appears
|
|
52
|
-
* in a finding
|
|
52
|
+
* in a finding, and a provider's refusal reaches a finding redacted (`ProviderError`).
|
|
53
53
|
*/
|
|
54
54
|
export declare function doctor(options?: DoctorOptions): Promise<DoctorResult>;
|
|
55
55
|
/** The report as printed: a blank line and a header per app, then its findings. */
|
package/dist/doctor.js
CHANGED
|
@@ -20,7 +20,7 @@ export const CORE_BUMP_BRANCH_PREFIX = "core-bump/";
|
|
|
20
20
|
* whole point of this command is one screen that says whether anything needs attention.
|
|
21
21
|
*
|
|
22
22
|
* Nothing here prints a secret. The read token authorizes the status request and never appears
|
|
23
|
-
* in a finding
|
|
23
|
+
* in a finding, and a provider's refusal reaches a finding redacted (`ProviderError`).
|
|
24
24
|
*/
|
|
25
25
|
export async function doctor(options = {}) {
|
|
26
26
|
const env = options.env ?? process.env;
|
|
@@ -29,14 +29,28 @@ export interface CoolifyApplicationRequest {
|
|
|
29
29
|
build_pack: "nixpacks" | "railpack" | "static" | "dockerfile" | "dockercompose";
|
|
30
30
|
name?: string;
|
|
31
31
|
description?: string;
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Comma-separated. Rejected outright for a `dockercompose` build pack — Coolify 4.3.21 answers
|
|
34
|
+
* 422 `The domains field cannot be used for dockercompose applications` — so
|
|
35
|
+
* `docker_compose_domains` is what `hf new` sends and this is for the other four packs.
|
|
36
|
+
*/
|
|
33
37
|
domains?: string;
|
|
38
|
+
/** One entry per compose service that gets a domain; `hf new` sends exactly one. */
|
|
39
|
+
docker_compose_domains?: readonly CoolifyComposeDomain[];
|
|
34
40
|
ports_exposes?: string;
|
|
35
41
|
docker_compose_location?: string;
|
|
36
42
|
connect_to_docker_network?: boolean;
|
|
37
43
|
instant_deploy?: boolean;
|
|
38
44
|
is_auto_deploy_enabled?: boolean;
|
|
39
45
|
}
|
|
46
|
+
/** A domain on one service of a compose application, which is how a `dockercompose` app gets one. */
|
|
47
|
+
export interface CoolifyComposeDomain {
|
|
48
|
+
/** The service name as `docker-compose.prod.yml` spells it. */
|
|
49
|
+
name: string;
|
|
50
|
+
/** Comma-separated, same as `domains`. */
|
|
51
|
+
domain: string;
|
|
52
|
+
redirect?: "www" | "non-www" | "both";
|
|
53
|
+
}
|
|
40
54
|
export interface CoolifyApplication {
|
|
41
55
|
uuid: string;
|
|
42
56
|
}
|
|
@@ -118,6 +132,11 @@ export declare class CoolifyClient {
|
|
|
118
132
|
listProjects(): Promise<CoolifyProject[]>;
|
|
119
133
|
getProject(uuid: string): Promise<CoolifyProject>;
|
|
120
134
|
listEnvironments(projectUuid: string): Promise<CoolifyEnvironment[]>;
|
|
135
|
+
createEnvironment(projectUuid: string, body: {
|
|
136
|
+
name: string;
|
|
137
|
+
}): Promise<{
|
|
138
|
+
uuid: string;
|
|
139
|
+
}>;
|
|
121
140
|
createPrivateGithubAppApplication(body: CoolifyApplicationRequest): Promise<CoolifyApplication>;
|
|
122
141
|
/**
|
|
123
142
|
* Every application, so a rerun can find the one it created last time by name.
|
|
@@ -6,6 +6,7 @@ export class CoolifyClient {
|
|
|
6
6
|
provider: "coolify",
|
|
7
7
|
baseUrl: `${options.url.replace(/\/+$/, "")}/api/v1`,
|
|
8
8
|
headers: { authorization: `Bearer ${options.token}` },
|
|
9
|
+
secrets: [options.token],
|
|
9
10
|
fetch: options.fetch,
|
|
10
11
|
});
|
|
11
12
|
}
|
|
@@ -25,6 +26,13 @@ export class CoolifyClient {
|
|
|
25
26
|
path: `/projects/${segment(projectUuid)}/environments`,
|
|
26
27
|
});
|
|
27
28
|
}
|
|
29
|
+
async createEnvironment(projectUuid, body) {
|
|
30
|
+
return await this.request({
|
|
31
|
+
method: "POST",
|
|
32
|
+
path: `/projects/${segment(projectUuid)}/environments`,
|
|
33
|
+
body,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
28
36
|
async createPrivateGithubAppApplication(body) {
|
|
29
37
|
return await this.request({ method: "POST", path: "/applications/private-github-app", body });
|
|
30
38
|
}
|
|
@@ -42,6 +50,9 @@ export class CoolifyClient {
|
|
|
42
50
|
method: "PATCH",
|
|
43
51
|
path: `/applications/${segment(appUuid)}/envs/bulk`,
|
|
44
52
|
body: { data },
|
|
53
|
+
// The app's whole environment goes out in this one call, and a 422 names the variables it
|
|
54
|
+
// rejected by quoting them.
|
|
55
|
+
secrets: data.map((variable) => variable.value),
|
|
45
56
|
});
|
|
46
57
|
}
|
|
47
58
|
async deploy(uuid, options = {}) {
|
package/dist/providers/github.js
CHANGED
package/dist/providers/http.d.ts
CHANGED
|
@@ -7,15 +7,23 @@ export interface ProviderRequest {
|
|
|
7
7
|
path: string;
|
|
8
8
|
query?: Record<string, string | number | boolean | undefined>;
|
|
9
9
|
body?: unknown;
|
|
10
|
+
/**
|
|
11
|
+
* Values this request carries that a provider may echo back at it — the app's environment, in
|
|
12
|
+
* the one call that sends it. Blanked out of the error message; see `explain`.
|
|
13
|
+
*/
|
|
14
|
+
secrets?: readonly string[];
|
|
10
15
|
}
|
|
16
|
+
/** How much of a rejected request's explanation reaches the message before it is cut. */
|
|
17
|
+
export declare const EXPLANATION_LIMIT = 500;
|
|
11
18
|
/**
|
|
12
19
|
* A provider answered with a status outside 2xx.
|
|
13
20
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* for
|
|
21
|
+
* `message` carries the response's own `message` and `errors` and nothing else of it: those two are
|
|
22
|
+
* where every provider here puts the reason, and the rest of a body is free to quote what was sent.
|
|
23
|
+
* What reaches the message is redacted first — every credential the client was built with, every
|
|
24
|
+
* value the request declared, and anything shaped like a password — because `hf` prints
|
|
25
|
+
* `error.message` and a terminal keeps a scrollback. The raw body is on `body` for a caller that
|
|
26
|
+
* wants it; the request's own headers and query string are in neither.
|
|
19
27
|
*/
|
|
20
28
|
export declare class ProviderError extends Error {
|
|
21
29
|
readonly provider: string;
|
|
@@ -23,7 +31,7 @@ export declare class ProviderError extends Error {
|
|
|
23
31
|
readonly method: HttpMethod;
|
|
24
32
|
readonly path: string;
|
|
25
33
|
readonly body: string;
|
|
26
|
-
constructor(provider: string, request: ProviderRequest, status: number, body: string);
|
|
34
|
+
constructor(provider: string, request: ProviderRequest, status: number, body: string, explanation?: string);
|
|
27
35
|
}
|
|
28
36
|
export interface TransportOptions {
|
|
29
37
|
/** Names the provider in errors. */
|
|
@@ -32,8 +40,17 @@ export interface TransportOptions {
|
|
|
32
40
|
baseUrl: string;
|
|
33
41
|
/** Sent on every request — the authorization header, and whatever else the API insists on. */
|
|
34
42
|
headers: Record<string, string>;
|
|
43
|
+
/** The credentials this client was built with; blanked out of every error message. */
|
|
44
|
+
secrets?: readonly string[];
|
|
35
45
|
fetch?: FetchLike;
|
|
36
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Why the provider refused, out of its `message` and `errors` and redacted.
|
|
49
|
+
*
|
|
50
|
+
* `undefined` when the body is not JSON or carries neither key: a provider that explained nothing
|
|
51
|
+
* leaves the status to speak, rather than a page of HTML in a terminal.
|
|
52
|
+
*/
|
|
53
|
+
export declare function explain(body: string, secrets: readonly string[]): string | undefined;
|
|
37
54
|
export type Transport = <Result>(request: ProviderRequest) => Promise<Result>;
|
|
38
55
|
/** Builds the `request` function the clients in this directory are written against. */
|
|
39
56
|
export declare function createTransport(options: TransportOptions): Transport;
|
package/dist/providers/http.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import { redactPasswords } from "../database.js";
|
|
2
|
+
/** How much of a rejected request's explanation reaches the message before it is cut. */
|
|
3
|
+
export const EXPLANATION_LIMIT = 500;
|
|
1
4
|
/**
|
|
2
5
|
* A provider answered with a status outside 2xx.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* for
|
|
7
|
+
* `message` carries the response's own `message` and `errors` and nothing else of it: those two are
|
|
8
|
+
* where every provider here puts the reason, and the rest of a body is free to quote what was sent.
|
|
9
|
+
* What reaches the message is redacted first — every credential the client was built with, every
|
|
10
|
+
* value the request declared, and anything shaped like a password — because `hf` prints
|
|
11
|
+
* `error.message` and a terminal keeps a scrollback. The raw body is on `body` for a caller that
|
|
12
|
+
* wants it; the request's own headers and query string are in neither.
|
|
9
13
|
*/
|
|
10
14
|
export class ProviderError extends Error {
|
|
11
15
|
provider;
|
|
@@ -13,8 +17,9 @@ export class ProviderError extends Error {
|
|
|
13
17
|
method;
|
|
14
18
|
path;
|
|
15
19
|
body;
|
|
16
|
-
constructor(provider, request, status, body) {
|
|
17
|
-
super(`${provider} ${request.method} ${request.path} failed: HTTP ${status}`
|
|
20
|
+
constructor(provider, request, status, body, explanation) {
|
|
21
|
+
super(`${provider} ${request.method} ${request.path} failed: HTTP ${status}` +
|
|
22
|
+
(explanation === undefined ? "" : `: ${explanation}`));
|
|
18
23
|
this.name = "ProviderError";
|
|
19
24
|
this.provider = provider;
|
|
20
25
|
this.status = status;
|
|
@@ -23,6 +28,38 @@ export class ProviderError extends Error {
|
|
|
23
28
|
this.body = body;
|
|
24
29
|
}
|
|
25
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Why the provider refused, out of its `message` and `errors` and redacted.
|
|
33
|
+
*
|
|
34
|
+
* `undefined` when the body is not JSON or carries neither key: a provider that explained nothing
|
|
35
|
+
* leaves the status to speak, rather than a page of HTML in a terminal.
|
|
36
|
+
*/
|
|
37
|
+
export function explain(body, secrets) {
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = JSON.parse(body);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
if (parsed === null || typeof parsed !== "object")
|
|
46
|
+
return undefined;
|
|
47
|
+
const { message, errors } = parsed;
|
|
48
|
+
if (message === undefined && errors === undefined)
|
|
49
|
+
return undefined;
|
|
50
|
+
let text = JSON.stringify({
|
|
51
|
+
...(message === undefined ? {} : { message }),
|
|
52
|
+
...(errors === undefined ? {} : { errors }),
|
|
53
|
+
});
|
|
54
|
+
for (const secret of secrets) {
|
|
55
|
+
// Short values are skipped: a two-character secret would blank half the explanation with it.
|
|
56
|
+
if (secret.length < 4)
|
|
57
|
+
continue;
|
|
58
|
+
text = text.split(JSON.stringify(secret).slice(1, -1)).join("***");
|
|
59
|
+
}
|
|
60
|
+
text = redactPasswords(text);
|
|
61
|
+
return text.length > EXPLANATION_LIMIT ? `${text.slice(0, EXPLANATION_LIMIT)}…` : text;
|
|
62
|
+
}
|
|
26
63
|
/** Builds the `request` function the clients in this directory are written against. */
|
|
27
64
|
export function createTransport(options) {
|
|
28
65
|
// Looked up per request, not captured: a test's mock server replaces `globalThis.fetch` after
|
|
@@ -45,7 +82,8 @@ export function createTransport(options) {
|
|
|
45
82
|
});
|
|
46
83
|
const text = await response.text();
|
|
47
84
|
if (!response.ok) {
|
|
48
|
-
|
|
85
|
+
const secrets = [...(options.secrets ?? []), ...(request.secrets ?? [])];
|
|
86
|
+
throw new ProviderError(options.provider, request, response.status, text, explain(text, secrets));
|
|
49
87
|
}
|
|
50
88
|
return (text === "" ? undefined : JSON.parse(text));
|
|
51
89
|
};
|
|
@@ -8,6 +8,8 @@ export class LangfuseClient {
|
|
|
8
8
|
headers: {
|
|
9
9
|
authorization: `Basic ${Buffer.from(options.orgKey, "utf8").toString("base64")}`,
|
|
10
10
|
},
|
|
11
|
+
// Both halves of the pair: an error message must not quote either one back.
|
|
12
|
+
secrets: options.orgKey.split(":"),
|
|
11
13
|
fetch: options.fetch,
|
|
12
14
|
});
|
|
13
15
|
}
|
package/dist/providers/sentry.js
CHANGED
package/dist/state.d.ts
CHANGED
|
@@ -50,6 +50,15 @@ export interface StatusTokenState {
|
|
|
50
50
|
*/
|
|
51
51
|
export interface AppState {
|
|
52
52
|
steps: Partial<Record<StepName, StepRecord>>;
|
|
53
|
+
/**
|
|
54
|
+
* ISO 8601, written just before the `template` step renames the substituted fetch into place.
|
|
55
|
+
*
|
|
56
|
+
* The proof that a directory at the app's path is this run's own: `steps.template` is only
|
|
57
|
+
* recorded after the rename, so a crash in between would otherwise leave a directory no state
|
|
58
|
+
* vouches for. Without it the step refuses to adopt a directory — a stale scaffold from an
|
|
59
|
+
* earlier `hf new --local` looks exactly like a lost run.
|
|
60
|
+
*/
|
|
61
|
+
templateStartedAt?: string;
|
|
53
62
|
/** `owner/name` of the app's GitHub repository. */
|
|
54
63
|
repo?: string;
|
|
55
64
|
coolify?: CoolifyState;
|
package/dist/state.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperfixation/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "The hf binary and its Turborepo generator templates",
|
|
6
6
|
"repository": {
|
|
@@ -29,15 +29,15 @@
|
|
|
29
29
|
"!dist/test-support/**"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@hyperfixation/auth": "0.1.
|
|
33
|
-
"@hyperfixation/core": "0.1.
|
|
34
|
-
"@hyperfixation/db": "0.1.
|
|
32
|
+
"@hyperfixation/auth": "0.1.4",
|
|
33
|
+
"@hyperfixation/core": "0.1.4",
|
|
34
|
+
"@hyperfixation/db": "0.1.4",
|
|
35
35
|
"giget": "3.3.1",
|
|
36
36
|
"pg": "^8.23.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@hyperfixation/eslint-config": "0.1.
|
|
40
|
-
"@hyperfixation/testing": "0.1.
|
|
39
|
+
"@hyperfixation/eslint-config": "0.1.4",
|
|
40
|
+
"@hyperfixation/testing": "0.1.4",
|
|
41
41
|
"@microsoft/api-extractor": "^7.59.1",
|
|
42
42
|
"@types/pg": "^8.23.1",
|
|
43
43
|
"eslint": "^10.10.0",
|