@hyperfixation/cli 0.1.0 → 0.1.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.
Files changed (75) hide show
  1. package/dist/app.d.ts +15 -2
  2. package/dist/app.js +4 -2
  3. package/dist/backup-source.d.ts +47 -0
  4. package/dist/backup-source.js +107 -0
  5. package/dist/bootstrap.d.ts +2 -0
  6. package/dist/bootstrap.js +1 -1
  7. package/dist/checklist.d.ts +25 -0
  8. package/dist/checklist.js +32 -0
  9. package/dist/cli.d.ts +2 -2
  10. package/dist/cli.js +95 -2
  11. package/dist/cloud-steps/backup.d.ts +17 -0
  12. package/dist/cloud-steps/backup.js +40 -0
  13. package/dist/cloud-steps/context.d.ts +120 -0
  14. package/dist/cloud-steps/context.js +88 -0
  15. package/dist/cloud-steps/coolify.d.ts +74 -0
  16. package/dist/cloud-steps/coolify.js +300 -0
  17. package/dist/cloud-steps/database.d.ts +12 -0
  18. package/dist/cloud-steps/database.js +25 -0
  19. package/dist/cloud-steps/deploy.d.ts +18 -0
  20. package/dist/cloud-steps/deploy.js +110 -0
  21. package/dist/cloud-steps/dns.d.ts +11 -0
  22. package/dist/cloud-steps/dns.js +53 -0
  23. package/dist/cloud-steps/index.d.ts +21 -0
  24. package/dist/cloud-steps/index.js +30 -0
  25. package/dist/cloud-steps/install.d.ts +12 -0
  26. package/dist/cloud-steps/install.js +53 -0
  27. package/dist/cloud-steps/langfuse.d.ts +12 -0
  28. package/dist/cloud-steps/langfuse.js +35 -0
  29. package/dist/cloud-steps/repo.d.ts +20 -0
  30. package/dist/cloud-steps/repo.js +163 -0
  31. package/dist/cloud-steps/sentry.d.ts +13 -0
  32. package/dist/cloud-steps/sentry.js +55 -0
  33. package/dist/cloud-steps/template.d.ts +22 -0
  34. package/dist/cloud-steps/template.js +68 -0
  35. package/dist/config.d.ts +53 -0
  36. package/dist/config.js +155 -0
  37. package/dist/database.d.ts +65 -0
  38. package/dist/database.js +142 -0
  39. package/dist/doctor.d.ts +71 -0
  40. package/dist/doctor.js +310 -0
  41. package/dist/index.d.ts +6 -1
  42. package/dist/index.js +5 -0
  43. package/dist/migrate.d.ts +11 -0
  44. package/dist/migrate.js +26 -2
  45. package/dist/new-cloud.d.ts +126 -0
  46. package/dist/new-cloud.js +210 -0
  47. package/dist/new.d.ts +2 -0
  48. package/dist/new.js +2 -1
  49. package/dist/providers/cloudflare.d.ts +49 -0
  50. package/dist/providers/cloudflare.js +27 -0
  51. package/dist/providers/coolify.d.ts +148 -0
  52. package/dist/providers/coolify.js +87 -0
  53. package/dist/providers/github.d.ts +117 -0
  54. package/dist/providers/github.js +98 -0
  55. package/dist/providers/http.d.ts +41 -0
  56. package/dist/providers/http.js +56 -0
  57. package/dist/providers/langfuse.d.ts +41 -0
  58. package/dist/providers/langfuse.js +29 -0
  59. package/dist/providers/sentry.d.ts +31 -0
  60. package/dist/providers/sentry.js +27 -0
  61. package/dist/provision-database.d.ts +42 -0
  62. package/dist/provision-database.js +107 -0
  63. package/dist/restore-check.d.ts +91 -0
  64. package/dist/restore-check.js +257 -0
  65. package/dist/runner.d.ts +65 -0
  66. package/dist/runner.js +199 -0
  67. package/dist/secret-file.d.ts +30 -0
  68. package/dist/secret-file.js +69 -0
  69. package/dist/state.d.ts +124 -0
  70. package/dist/state.js +217 -0
  71. package/dist/status-token.d.ts +2 -0
  72. package/dist/status-token.js +1 -1
  73. package/dist/template-source.d.ts +23 -0
  74. package/dist/template-source.js +23 -0
  75. package/package.json +10 -7
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The ten steps of a cloud `hf new`, in the order it runs them. A step is recorded only once
3
+ * everything it created is in the state file, so a rerun resumes at the first unrecorded step
4
+ * rather than re-creating what the previous run already paid for.
5
+ *
6
+ * `database` sits immediately before `coolify` because it is the one step that *rotates*: a cold
7
+ * run gives the roles new passwords, and a deployed app keeps the old ones until the envs are
8
+ * PATCHed and the app redeployed. Every fallible external create — the repo, the backup, Sentry,
9
+ * Langfuse, the DNS record — therefore happens first, so a failure in one of them cannot leave a
10
+ * live app holding credentials nothing will replace.
11
+ */
12
+ export declare const STEPS: readonly ["template", "install", "repo", "backup", "sentry", "langfuse", "dns", "database", "coolify", "deploy"];
13
+ export type StepName = (typeof STEPS)[number];
14
+ export interface StepRecord {
15
+ /** ISO 8601, when the step finished. */
16
+ doneAt: string;
17
+ }
18
+ export interface CoolifyState {
19
+ projectUuid?: string;
20
+ appUuid?: string;
21
+ /**
22
+ * `secretsHash` of the secrets the last bulk env PATCH carried.
23
+ *
24
+ * The `coolify` step is "done" only while this matches what the state holds now: a cold run
25
+ * that rotated the role passwords leaves the deployed app authenticating with the old ones, and
26
+ * a recorded `steps.coolify` would otherwise make a rerun skip the one PATCH that fixes it.
27
+ */
28
+ envsSecretsHash?: string;
29
+ }
30
+ /** The three role passwords a cold run generates. Nothing else on the laptop has a copy. */
31
+ export interface DatabaseState {
32
+ migratorPassword?: string;
33
+ applicationPassword?: string;
34
+ readonlyPassword?: string;
35
+ }
36
+ export interface LangfuseState {
37
+ publicKey?: string;
38
+ secretKey?: string;
39
+ }
40
+ export interface StatusTokenState {
41
+ read?: string;
42
+ write?: string;
43
+ }
44
+ /**
45
+ * Everything one cloud app's provisioning produced, so that a rerun, `hf doctor` and
46
+ * `hf restore-check` do not have to ask five APIs what already exists.
47
+ *
48
+ * Every field is optional but `steps`: the file is written between steps, and each step fills
49
+ * in its own part.
50
+ */
51
+ export interface AppState {
52
+ steps: Partial<Record<StepName, StepRecord>>;
53
+ /** `owner/name` of the app's GitHub repository. */
54
+ repo?: string;
55
+ coolify?: CoolifyState;
56
+ database?: DatabaseState;
57
+ sentryDsn?: string;
58
+ langfuse?: LangfuseState;
59
+ statusTokens?: StatusTokenState;
60
+ /**
61
+ * `BETTER_AUTH_SECRET` — generated once, for this app, and kept only here: it is the one
62
+ * `REQUIRED_ENV` secret no provider hands back, so nothing else can be asked for it again.
63
+ * Never printed; it reaches the app through the Coolify env PATCH alone.
64
+ */
65
+ betterAuthSecret?: string;
66
+ lastRestoreCheckAt?: string;
67
+ lastDeployedSha?: string;
68
+ }
69
+ /** 32 random bytes, the length `better-auth` documents, in the form an env var can carry. */
70
+ export declare function generateBetterAuthSecret(): string;
71
+ /**
72
+ * A fingerprint of every secret the Coolify envs carry, so a rotation is detectable without
73
+ * keeping a second copy of the secrets themselves.
74
+ *
75
+ * Only the values matter, not the state around them: `coolify.envsSecretsHash` is compared
76
+ * against this, and a step that rotated a password must come out different.
77
+ */
78
+ export declare function secretsHash(state: AppState): string;
79
+ /**
80
+ * The state file exists but does not parse, or does not have the shape above.
81
+ *
82
+ * Never repaired and never overwritten: the file is the only copy of three database passwords,
83
+ * two status tokens and a Langfuse secret key, so silently starting a fresh one would strand a
84
+ * deployed app whose roles nothing can log in as any more. The operator is told where the file
85
+ * is and decides.
86
+ */
87
+ export declare class AppStateInvalid extends Error {
88
+ readonly file: string;
89
+ constructor(file: string, problem: string);
90
+ }
91
+ export interface AppStateStore {
92
+ readonly file: string;
93
+ /** The state as last written. Replaced, never mutated in place, by `patch` and `markDone`. */
94
+ readonly state: AppState;
95
+ isDone(step: StepName): boolean;
96
+ markDone(step: StepName): Promise<void>;
97
+ /**
98
+ * Forgets a step, so the next run repeats it.
99
+ *
100
+ * What a rotation needs: the secrets the `coolify` step PATCHed and the `deploy` that published
101
+ * them are no longer true of the app, and `patch` merges rather than removes.
102
+ */
103
+ clearDone(step: StepName): Promise<void>;
104
+ /**
105
+ * Merges `changes` in and writes the file. Object-valued keys (`coolify`, `database`,
106
+ * `langfuse`, `statusTokens`) merge field by field, so a step can record the one uuid it
107
+ * learned without carrying the rest.
108
+ */
109
+ patch(changes: Partial<AppState>): Promise<void>;
110
+ }
111
+ /** `~/.config/hf/state`, one `<name>.json` per app. */
112
+ export declare function stateDir(env?: NodeJS.ProcessEnv): string;
113
+ export declare function stateFile(name: string, env?: NodeJS.ProcessEnv): string;
114
+ export interface OpenAppStateOptions {
115
+ /** The directory holding `<name>.json`. Defaults to `stateDir()`. */
116
+ dir?: string;
117
+ env?: NodeJS.ProcessEnv;
118
+ }
119
+ /**
120
+ * Opens one app's state cache, or starts an empty one when the file does not exist yet.
121
+ *
122
+ * The file must be mode 0600; a looser one is tightened and then refused (`InsecureFileMode`).
123
+ */
124
+ export declare function openAppState(name: string, options?: OpenAppStateOptions): Promise<AppStateStore>;
package/dist/state.js ADDED
@@ -0,0 +1,217 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import path from "node:path";
3
+ import { configHome } from "./config.js";
4
+ import { readSecretFile, writeSecretFile } from "./secret-file.js";
5
+ /**
6
+ * The ten steps of a cloud `hf new`, in the order it runs them. A step is recorded only once
7
+ * everything it created is in the state file, so a rerun resumes at the first unrecorded step
8
+ * rather than re-creating what the previous run already paid for.
9
+ *
10
+ * `database` sits immediately before `coolify` because it is the one step that *rotates*: a cold
11
+ * run gives the roles new passwords, and a deployed app keeps the old ones until the envs are
12
+ * PATCHed and the app redeployed. Every fallible external create — the repo, the backup, Sentry,
13
+ * Langfuse, the DNS record — therefore happens first, so a failure in one of them cannot leave a
14
+ * live app holding credentials nothing will replace.
15
+ */
16
+ export const STEPS = [
17
+ "template",
18
+ "install",
19
+ "repo",
20
+ "backup",
21
+ "sentry",
22
+ "langfuse",
23
+ "dns",
24
+ "database",
25
+ "coolify",
26
+ "deploy",
27
+ ];
28
+ /** 32 random bytes, the length `better-auth` documents, in the form an env var can carry. */
29
+ export function generateBetterAuthSecret() {
30
+ return randomBytes(32).toString("base64url");
31
+ }
32
+ /**
33
+ * A fingerprint of every secret the Coolify envs carry, so a rotation is detectable without
34
+ * keeping a second copy of the secrets themselves.
35
+ *
36
+ * Only the values matter, not the state around them: `coolify.envsSecretsHash` is compared
37
+ * against this, and a step that rotated a password must come out different.
38
+ */
39
+ export function secretsHash(state) {
40
+ const database = state.database ?? {};
41
+ const material = [
42
+ database.migratorPassword,
43
+ database.applicationPassword,
44
+ database.readonlyPassword,
45
+ state.betterAuthSecret,
46
+ state.statusTokens?.read,
47
+ state.statusTokens?.write,
48
+ ];
49
+ return createHash("sha256").update(JSON.stringify(material)).digest("hex");
50
+ }
51
+ /**
52
+ * The state file exists but does not parse, or does not have the shape above.
53
+ *
54
+ * Never repaired and never overwritten: the file is the only copy of three database passwords,
55
+ * two status tokens and a Langfuse secret key, so silently starting a fresh one would strand a
56
+ * deployed app whose roles nothing can log in as any more. The operator is told where the file
57
+ * is and decides.
58
+ */
59
+ export class AppStateInvalid extends Error {
60
+ file;
61
+ constructor(file, problem) {
62
+ super(`${file} is not a usable hf state file: ${problem}. It is the only copy of this app's ` +
63
+ `passwords and tokens, so nothing was overwritten — inspect it, or move it aside to ` +
64
+ `start over.`);
65
+ this.name = "AppStateInvalid";
66
+ this.file = file;
67
+ }
68
+ }
69
+ /** `~/.config/hf/state`, one `<name>.json` per app. */
70
+ export function stateDir(env = process.env) {
71
+ return path.join(configHome(env), "state");
72
+ }
73
+ export function stateFile(name, env = process.env) {
74
+ return path.join(stateDir(env), `${name}.json`);
75
+ }
76
+ /**
77
+ * Opens one app's state cache, or starts an empty one when the file does not exist yet.
78
+ *
79
+ * The file must be mode 0600; a looser one is tightened and then refused (`InsecureFileMode`).
80
+ */
81
+ export async function openAppState(name, options = {}) {
82
+ const env = options.env ?? process.env;
83
+ const file = options.dir === undefined ? stateFile(name, env) : path.join(options.dir, `${name}.json`);
84
+ const contents = await readSecretFile(file);
85
+ let state = contents === undefined ? { steps: {} } : parseAppState(contents, file);
86
+ const write = async (next) => {
87
+ await writeSecretFile(file, `${JSON.stringify(next, null, 2)}\n`);
88
+ state = next;
89
+ };
90
+ return {
91
+ file,
92
+ get state() {
93
+ return state;
94
+ },
95
+ isDone: (step) => state.steps[step] !== undefined,
96
+ markDone: async (step) => {
97
+ await write({
98
+ ...state,
99
+ steps: { ...state.steps, [step]: { doneAt: new Date().toISOString() } },
100
+ });
101
+ },
102
+ clearDone: async (step) => {
103
+ if (state.steps[step] === undefined)
104
+ return;
105
+ const steps = { ...state.steps };
106
+ delete steps[step];
107
+ await write({ ...state, steps });
108
+ },
109
+ patch: async (changes) => {
110
+ await write(merge(state, changes));
111
+ },
112
+ };
113
+ }
114
+ function merge(state, changes) {
115
+ return {
116
+ ...state,
117
+ ...changes,
118
+ steps: { ...state.steps, ...changes.steps },
119
+ ...mergeObject(state, changes, "coolify"),
120
+ ...mergeObject(state, changes, "database"),
121
+ ...mergeObject(state, changes, "langfuse"),
122
+ ...mergeObject(state, changes, "statusTokens"),
123
+ };
124
+ }
125
+ function mergeObject(state, changes, key) {
126
+ const change = changes[key];
127
+ if (change === undefined)
128
+ return {};
129
+ return { [key]: { ...state[key], ...change } };
130
+ }
131
+ /**
132
+ * The schema, hand-written because the whole contract is nine keys and a step map, and because
133
+ * an unknown key has to be an error rather than something a permissive parser drops on the next
134
+ * write.
135
+ */
136
+ function parseAppState(contents, file) {
137
+ let parsed;
138
+ try {
139
+ parsed = JSON.parse(contents);
140
+ }
141
+ catch {
142
+ throw new AppStateInvalid(file, "it is not valid JSON");
143
+ }
144
+ const root = asObject(parsed, file, "the top level");
145
+ const state = { steps: {} };
146
+ for (const [key, value] of Object.entries(root)) {
147
+ switch (key) {
148
+ case "steps":
149
+ state.steps = parseSteps(value, file);
150
+ break;
151
+ case "repo":
152
+ case "sentryDsn":
153
+ case "betterAuthSecret":
154
+ case "lastRestoreCheckAt":
155
+ case "lastDeployedSha":
156
+ state[key] = asString(value, file, key);
157
+ break;
158
+ case "coolify":
159
+ state.coolify = parseFields(value, file, key, [
160
+ "projectUuid",
161
+ "appUuid",
162
+ "envsSecretsHash",
163
+ ]);
164
+ break;
165
+ case "database":
166
+ state.database = parseFields(value, file, key, [
167
+ "migratorPassword",
168
+ "applicationPassword",
169
+ "readonlyPassword",
170
+ ]);
171
+ break;
172
+ case "langfuse":
173
+ state.langfuse = parseFields(value, file, key, ["publicKey", "secretKey"]);
174
+ break;
175
+ case "statusTokens":
176
+ state.statusTokens = parseFields(value, file, key, ["read", "write"]);
177
+ break;
178
+ default:
179
+ throw new AppStateInvalid(file, `${JSON.stringify(key)} is not an hf state key`);
180
+ }
181
+ }
182
+ if (root.steps === undefined)
183
+ throw new AppStateInvalid(file, "it has no steps");
184
+ return state;
185
+ }
186
+ function parseSteps(value, file) {
187
+ const steps = {};
188
+ for (const [name, record] of Object.entries(asObject(value, file, "steps"))) {
189
+ if (!STEPS.includes(name)) {
190
+ throw new AppStateInvalid(file, `steps.${name} is not a step of hf new`);
191
+ }
192
+ const fields = asObject(record, file, `steps.${name}`);
193
+ steps[name] = { doneAt: asString(fields.doneAt, file, `steps.${name}.doneAt`) };
194
+ }
195
+ return steps;
196
+ }
197
+ function parseFields(value, file, key, fields) {
198
+ const out = {};
199
+ for (const [field, entry] of Object.entries(asObject(value, file, key))) {
200
+ if (!fields.includes(field)) {
201
+ throw new AppStateInvalid(file, `${key}.${field} is not an hf state key`);
202
+ }
203
+ out[field] = asString(entry, file, `${key}.${field}`);
204
+ }
205
+ return out;
206
+ }
207
+ function asObject(value, file, what) {
208
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
209
+ throw new AppStateInvalid(file, `${what} is not an object`);
210
+ }
211
+ return value;
212
+ }
213
+ function asString(value, file, what) {
214
+ if (typeof value !== "string")
215
+ throw new AppStateInvalid(file, `${what} is not a string`);
216
+ return value;
217
+ }
@@ -19,6 +19,8 @@ export interface StatusTokenAppOptions {
19
19
  * up yet" would also require `--rotate`, and `--rotate` would take out the other, live token.
20
20
  */
21
21
  explicit?: boolean;
22
+ /** Connection URLs in place of a `.env`; see `ResolveAppOptions`. */
23
+ env?: Record<string, string>;
22
24
  }
23
25
  export interface StatusTokenAppResult {
24
26
  app: ResolvedApp;
@@ -35,7 +35,7 @@ export class StatusTokenAlreadySet extends Error {
35
35
  * caller; nothing here ever stores or logs it.
36
36
  */
37
37
  export async function statusTokenApp(options = {}) {
38
- const app = await resolveApp(options.dir);
38
+ const app = await resolveApp(options.dir, { env: options.env });
39
39
  const databaseUrl = requireEnv(app, "DATABASE_URL");
40
40
  const kinds = options.kinds ?? ["read", "write"];
41
41
  const pool = new Pool({ connectionString: databaseUrl, max: 1 });
@@ -1,5 +1,7 @@
1
1
  /** Points `hf new` at a template checkout; what a CI job or a second checkout sets. */
2
2
  export declare const TEMPLATE_DIR_ENV = "HF_TEMPLATE_DIR";
3
+ /** What a cloud `hf new` fetches when `--from` is absent; giget's `gh:` provider resolves it. */
4
+ export declare const TEMPLATE_REPOSITORY = "gh:grahamlutz/hyperfixation-template";
3
5
  /**
4
6
  * Where `hf new --local` looks when `--from` is not given: the environment, then the sibling
5
7
  * checkout beside the working directory, then the sibling beside this package's own repo.
@@ -14,3 +16,24 @@ export declare const TEMPLATE_DIR_ENV = "HF_TEMPLATE_DIR";
14
16
  export declare function findTemplateSource(cwd?: string): Promise<string | undefined>;
15
17
  /** The same search, but a miss is the error the user has to act on rather than `undefined`. */
16
18
  export declare function requireTemplateSource(cwd?: string): Promise<string>;
19
+ /** giget's `downloadTemplate`, narrowed to what `fetchTemplate` asks of it so a test can be one. */
20
+ export type TemplateDownload = (source: string, options: {
21
+ dir: string;
22
+ force: boolean;
23
+ }) => Promise<{
24
+ dir: string;
25
+ }>;
26
+ export interface FetchTemplateOptions {
27
+ /** Replaces the real download; the tests never reach the network. */
28
+ download?: TemplateDownload;
29
+ }
30
+ /**
31
+ * Fetches the template into `dir` — the cloud path's counterpart to `requireTemplateSource`.
32
+ *
33
+ * `source` is a giget specifier (`gh:owner/repo`, optionally `#ref`), defaulting to
34
+ * `TEMPLATE_REPOSITORY`; `--from` passes whatever the operator typed straight through, so a
35
+ * branch or a fork needs no flag of its own. What arrives is checked for `TEMPLATE_MARKER`
36
+ * before `newApp` is let near it: a specifier that resolves to some other repository would
37
+ * otherwise be copied, substituted and committed as an app.
38
+ */
39
+ export declare function fetchTemplate(source: string | undefined, dir: string, options?: FetchTemplateOptions): Promise<string>;
@@ -1,9 +1,12 @@
1
1
  import { stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ import { downloadTemplate } from "giget";
4
5
  import { TEMPLATE_MARKER, TemplateError } from "./new.js";
5
6
  /** Points `hf new` at a template checkout; what a CI job or a second checkout sets. */
6
7
  export const TEMPLATE_DIR_ENV = "HF_TEMPLATE_DIR";
8
+ /** What a cloud `hf new` fetches when `--from` is absent; giget's `gh:` provider resolves it. */
9
+ export const TEMPLATE_REPOSITORY = "gh:grahamlutz/hyperfixation-template";
7
10
  /**
8
11
  * Where `hf new --local` looks when `--from` is not given: the environment, then the sibling
9
12
  * checkout beside the working directory, then the sibling beside this package's own repo.
@@ -36,6 +39,26 @@ export async function requireTemplateSource(cwd) {
36
39
  }
37
40
  return found;
38
41
  }
42
+ /**
43
+ * Fetches the template into `dir` — the cloud path's counterpart to `requireTemplateSource`.
44
+ *
45
+ * `source` is a giget specifier (`gh:owner/repo`, optionally `#ref`), defaulting to
46
+ * `TEMPLATE_REPOSITORY`; `--from` passes whatever the operator typed straight through, so a
47
+ * branch or a fork needs no flag of its own. What arrives is checked for `TEMPLATE_MARKER`
48
+ * before `newApp` is let near it: a specifier that resolves to some other repository would
49
+ * otherwise be copied, substituted and committed as an app.
50
+ */
51
+ export async function fetchTemplate(source, dir, options = {}) {
52
+ const specifier = source ?? TEMPLATE_REPOSITORY;
53
+ const download = options.download ?? defaultDownload;
54
+ const target = path.resolve(dir);
55
+ const { dir: fetched } = await download(specifier, { dir: target, force: true });
56
+ if (!(await isTemplate(fetched))) {
57
+ throw new TemplateError(`${specifier} has no ${TEMPLATE_MARKER}: it is not the hyperfixation template`);
58
+ }
59
+ return fetched;
60
+ }
61
+ const defaultDownload = async (source, options) => await downloadTemplate(source, { ...options, silent: true });
39
62
  async function isTemplate(dir) {
40
63
  try {
41
64
  return (await stat(path.join(dir, TEMPLATE_MARKER))).isFile();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperfixation/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "license": "MIT",
5
5
  "description": "The hf binary and its Turborepo generator templates",
6
6
  "repository": {
@@ -29,17 +29,20 @@
29
29
  "!dist/test-support/**"
30
30
  ],
31
31
  "dependencies": {
32
- "@hyperfixation/auth": "0.1.0",
33
- "@hyperfixation/core": "0.1.0",
34
- "@hyperfixation/db": "0.1.0",
32
+ "@hyperfixation/auth": "0.1.1",
33
+ "@hyperfixation/core": "0.1.1",
34
+ "@hyperfixation/db": "0.1.1",
35
+ "giget": "3.3.1",
35
36
  "pg": "^8.23.0"
36
37
  },
37
38
  "devDependencies": {
38
- "@hyperfixation/eslint-config": "0.1.0",
39
- "@hyperfixation/testing": "0.1.0",
39
+ "@hyperfixation/eslint-config": "0.1.1",
40
+ "@hyperfixation/testing": "0.1.1",
40
41
  "@microsoft/api-extractor": "^7.59.1",
41
42
  "@types/pg": "^8.23.1",
42
- "eslint": "^10.10.0"
43
+ "eslint": "^10.10.0",
44
+ "msw": "2.15.0",
45
+ "openapi-request-validator": "12.1.3"
43
46
  },
44
47
  "scripts": {
45
48
  "build": "tsc -p tsconfig.json",