@hyperfixation/cli 0.1.3 → 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.
@@ -233,12 +233,11 @@ async function findOrCreateProject(context, coolify) {
233
233
  async function productionEnvironment(context, coolify, projectUuid) {
234
234
  const environments = await coolify.listEnvironments(projectUuid);
235
235
  const production = environments.find((environment) => environment.name === COOLIFY_ENVIRONMENT);
236
- if (production === undefined) {
237
- throw new StepFailed(`the Coolify project ${context.names.given} has no ${COOLIFY_ENVIRONMENT} environment ` +
238
- `(it has ${environments.map((environment) => environment.name).join(", ") || "none"}), ` +
239
- "and the API has no endpoint that creates one: add it in Coolify and re-run hf new");
240
- }
241
- 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;
242
241
  }
243
242
  async function findOrCreateApplication(context, coolify, projectUuid, environmentUuid) {
244
243
  const name = context.names.given;
@@ -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 whose state was lost, or something else.
48
+ * A directory already at the target: this app on a run that crashed after the rename, or
49
+ * something else.
46
50
  *
47
- * "This app" means a substituted templateits `package.json` carries the underscored app name
48
- * and the marker is gone. Anything else is the local flow's rule, refused rather than written
49
- * into.
51
+ * Adopted only on a genuine resumethe 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`);
@@ -132,6 +132,11 @@ export declare class CoolifyClient {
132
132
  listProjects(): Promise<CoolifyProject[]>;
133
133
  getProject(uuid: string): Promise<CoolifyProject>;
134
134
  listEnvironments(projectUuid: string): Promise<CoolifyEnvironment[]>;
135
+ createEnvironment(projectUuid: string, body: {
136
+ name: string;
137
+ }): Promise<{
138
+ uuid: string;
139
+ }>;
135
140
  createPrivateGithubAppApplication(body: CoolifyApplicationRequest): Promise<CoolifyApplication>;
136
141
  /**
137
142
  * Every application, so a rerun can find the one it created last time by name.
@@ -26,6 +26,13 @@ export class CoolifyClient {
26
26
  path: `/projects/${segment(projectUuid)}/environments`,
27
27
  });
28
28
  }
29
+ async createEnvironment(projectUuid, body) {
30
+ return await this.request({
31
+ method: "POST",
32
+ path: `/projects/${segment(projectUuid)}/environments`,
33
+ body,
34
+ });
35
+ }
29
36
  async createPrivateGithubAppApplication(body) {
30
37
  return await this.request({ method: "POST", path: "/applications/private-github-app", body });
31
38
  }
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
@@ -149,6 +149,7 @@ function parseAppState(contents, file) {
149
149
  state.steps = parseSteps(value, file);
150
150
  break;
151
151
  case "repo":
152
+ case "templateStartedAt":
152
153
  case "sentryDsn":
153
154
  case "betterAuthSecret":
154
155
  case "lastRestoreCheckAt":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperfixation/cli",
3
- "version": "0.1.3",
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.3",
33
- "@hyperfixation/core": "0.1.3",
34
- "@hyperfixation/db": "0.1.3",
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.3",
40
- "@hyperfixation/testing": "0.1.3",
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",