@ebay-dt/bonfire-node 0.1.0-alpha.0 → 0.1.0-alpha.2

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 (77) hide show
  1. package/README.md +77 -4
  2. package/dist/agent-service.d.ts +42 -0
  3. package/dist/agent-service.d.ts.map +1 -0
  4. package/dist/agent-service.js +116 -0
  5. package/dist/agent-service.js.map +1 -0
  6. package/dist/claude.d.ts +50 -0
  7. package/dist/claude.d.ts.map +1 -0
  8. package/dist/claude.js +119 -0
  9. package/dist/claude.js.map +1 -0
  10. package/dist/cursor.d.ts +30 -0
  11. package/dist/cursor.d.ts.map +1 -0
  12. package/dist/cursor.js +57 -0
  13. package/dist/cursor.js.map +1 -0
  14. package/dist/deployment-provider.d.ts +32 -0
  15. package/dist/deployment-provider.d.ts.map +1 -0
  16. package/dist/deployment-provider.js +167 -0
  17. package/dist/deployment-provider.js.map +1 -0
  18. package/dist/duplicate.d.ts +3 -2
  19. package/dist/duplicate.d.ts.map +1 -1
  20. package/dist/duplicate.js +18 -18
  21. package/dist/duplicate.js.map +1 -1
  22. package/dist/executable.d.ts +16 -0
  23. package/dist/executable.d.ts.map +1 -0
  24. package/dist/executable.js +65 -0
  25. package/dist/executable.js.map +1 -0
  26. package/dist/git-repository.d.ts.map +1 -1
  27. package/dist/git-repository.js.map +1 -1
  28. package/dist/github-cli.d.ts.map +1 -1
  29. package/dist/github-cli.js +23 -3
  30. package/dist/github-cli.js.map +1 -1
  31. package/dist/index.d.ts +7 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +7 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/internal/owned-destination.d.ts +2 -2
  36. package/dist/internal/owned-destination.d.ts.map +1 -1
  37. package/dist/internal/owned-destination.js +2 -2
  38. package/dist/internal/owned-destination.js.map +1 -1
  39. package/dist/internal/prototype-templates.d.ts +3 -2
  40. package/dist/internal/prototype-templates.d.ts.map +1 -1
  41. package/dist/internal/prototype-templates.js +13 -8
  42. package/dist/internal/prototype-templates.js.map +1 -1
  43. package/dist/internal/utils.d.ts +2 -0
  44. package/dist/internal/utils.d.ts.map +1 -1
  45. package/dist/internal/utils.js +6 -0
  46. package/dist/internal/utils.js.map +1 -1
  47. package/dist/lifecycle/dirty-state.d.ts.map +1 -1
  48. package/dist/lifecycle/dirty-state.js.map +1 -1
  49. package/dist/lifecycle/local-prototype-lifecycle.d.ts +22 -10
  50. package/dist/lifecycle/local-prototype-lifecycle.d.ts.map +1 -1
  51. package/dist/lifecycle/local-prototype-lifecycle.js +255 -6
  52. package/dist/lifecycle/local-prototype-lifecycle.js.map +1 -1
  53. package/dist/lifecycle/operation-gate.d.ts.map +1 -1
  54. package/dist/lifecycle/operation-gate.js.map +1 -1
  55. package/dist/lifecycle/runtime-context.d.ts.map +1 -1
  56. package/dist/lifecycle/runtime-context.js.map +1 -1
  57. package/dist/preferences.d.ts +19 -0
  58. package/dist/preferences.d.ts.map +1 -0
  59. package/dist/preferences.js +182 -0
  60. package/dist/preferences.js.map +1 -0
  61. package/dist/prepare-prototype-files.d.ts.map +1 -1
  62. package/dist/prepare-prototype-files.js.map +1 -1
  63. package/dist/process-runner.d.ts.map +1 -1
  64. package/dist/process-runner.js.map +1 -1
  65. package/dist/prototype-catalog.d.ts.map +1 -1
  66. package/dist/prototype-catalog.js +18 -7
  67. package/dist/prototype-catalog.js.map +1 -1
  68. package/dist/prototype-paths.d.ts +2 -0
  69. package/dist/prototype-paths.d.ts.map +1 -1
  70. package/dist/prototype-paths.js.map +1 -1
  71. package/dist/scaffold.d.ts +4 -2
  72. package/dist/scaffold.d.ts.map +1 -1
  73. package/dist/scaffold.js +7 -12
  74. package/dist/scaffold.js.map +1 -1
  75. package/dist/watcher.d.ts.map +1 -1
  76. package/dist/watcher.js.map +1 -1
  77. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/internal/utils.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CAE/D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEhE;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAExD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAExD"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/internal/utils.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CAE/D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAElE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEtD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEhE;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAExD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAExD","sourcesContent":["export type UnknownRecord = Record<string, unknown>;\n\nexport type ProcessFailure = {\n code?: unknown;\n killed?: unknown;\n signal?: unknown;\n message?: unknown;\n};\n\nexport function isRecord(value: unknown): value is UnknownRecord {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function hasErrorCode(value: unknown, code: string): boolean {\n return isRecord(value) && \"code\" in value && value.code === code;\n}\n\nexport function isFileNotFound(value: unknown): boolean {\n return hasErrorCode(value, \"ENOENT\");\n}\n\nexport function isNonEmptyString(value: unknown): value is string {\n return typeof value === \"string\" && value.length > 0;\n}\n\nexport function asFailure(value: unknown): ProcessFailure {\n return isRecord(value) ? value : {};\n}\n\nexport function isPositiveInteger(value: number): boolean {\n return Number.isInteger(value) && value > 0;\n}\n"]}
@@ -1,6 +1,12 @@
1
1
  export function isRecord(value) {
2
2
  return typeof value === "object" && value !== null && !Array.isArray(value);
3
3
  }
4
+ export function hasErrorCode(value, code) {
5
+ return isRecord(value) && "code" in value && value.code === code;
6
+ }
7
+ export function isFileNotFound(value) {
8
+ return hasErrorCode(value, "ENOENT");
9
+ }
4
10
  export function isNonEmptyString(value) {
5
11
  return typeof value === "string" && value.length > 0;
6
12
  }
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/internal/utils.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;AAC9C,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/internal/utils.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAc,EAAE,IAAY;IACvD,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;AAC9C,CAAC","sourcesContent":["export type UnknownRecord = Record<string, unknown>;\n\nexport type ProcessFailure = {\n code?: unknown;\n killed?: unknown;\n signal?: unknown;\n message?: unknown;\n};\n\nexport function isRecord(value: unknown): value is UnknownRecord {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function hasErrorCode(value: unknown, code: string): boolean {\n return isRecord(value) && \"code\" in value && value.code === code;\n}\n\nexport function isFileNotFound(value: unknown): boolean {\n return hasErrorCode(value, \"ENOENT\");\n}\n\nexport function isNonEmptyString(value: unknown): value is string {\n return typeof value === \"string\" && value.length > 0;\n}\n\nexport function asFailure(value: unknown): ProcessFailure {\n return isRecord(value) ? value : {};\n}\n\nexport function isPositiveInteger(value: number): boolean {\n return Number.isInteger(value) && value > 0;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"dirty-state.d.ts","sourceRoot":"","sources":["../../src/lifecycle/dirty-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACtB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,qBAAqB,CAAC;CAC9B,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,sBAAsB,GAAG,SAAS,EAC5C,YAAY,EAAE,OAAO,GACpB,oBAAoB,CAStB"}
1
+ {"version":3,"file":"dirty-state.d.ts","sourceRoot":"","sources":["../../src/lifecycle/dirty-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACtB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,qBAAqB,CAAC;CAC9B,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,sBAAsB,GAAG,SAAS,EAC5C,YAAY,EAAE,OAAO,GACpB,oBAAoB,CAStB","sourcesContent":["import type {\n DirtyObservationStatus,\n DirtyObservationValue,\n} from \"@ebay-dt/bonfire-core\";\n\nexport type DirtyStateTransition = {\n changed: boolean;\n value: DirtyObservationValue;\n};\n\nexport function applyDirtyObservation(\n previous: DirtyObservationStatus | undefined,\n currentDirty: boolean,\n): DirtyStateTransition {\n const value: DirtyObservationValue = {\n status: currentDirty ? \"dirty\" : \"clean\",\n };\n\n return {\n changed: previous === undefined || previous !== value.status,\n value,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"dirty-state.js","sourceRoot":"","sources":["../../src/lifecycle/dirty-state.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,qBAAqB,CACnC,QAA4C,EAC5C,YAAqB;IAErB,MAAM,KAAK,GAA0B;QACnC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO;KACzC,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,KAAK,CAAC,MAAM;QAC5D,KAAK;KACN,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"dirty-state.js","sourceRoot":"","sources":["../../src/lifecycle/dirty-state.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,qBAAqB,CACnC,QAA4C,EAC5C,YAAqB;IAErB,MAAM,KAAK,GAA0B;QACnC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO;KACzC,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,KAAK,CAAC,MAAM;QAC5D,KAAK;KACN,CAAC;AACJ,CAAC","sourcesContent":["import type {\n DirtyObservationStatus,\n DirtyObservationValue,\n} from \"@ebay-dt/bonfire-core\";\n\nexport type DirtyStateTransition = {\n changed: boolean;\n value: DirtyObservationValue;\n};\n\nexport function applyDirtyObservation(\n previous: DirtyObservationStatus | undefined,\n currentDirty: boolean,\n): DirtyStateTransition {\n const value: DirtyObservationValue = {\n status: currentDirty ? \"dirty\" : \"clean\",\n };\n\n return {\n changed: previous === undefined || previous !== value.status,\n value,\n };\n}\n"]}
@@ -1,14 +1,19 @@
1
- import { type BranchPreparationValue, type CreatePrototypeInput, type CreatePrototypeValue, type DirtyObservationValue, type DiscardPrototypeInput, type DiscardPrototypeValue, type DuplicatePrototypeInput, type DuplicatePrototypeValue, type LifecycleResult, type BonfireIdentity, type PrototypeCommandInput, type SavePrototypeInput, type SavePrototypeValue } from "@ebay-dt/bonfire-core";
1
+ import { type BranchPreparationValue, type CreatePrototypeInput, type CreatePrototypeValue, type DeletePrototypeValue, type DeploymentValue, type DirtyObservationValue, type DiscardPrototypeValue, type DuplicatePrototypeInput, type DuplicatePrototypeValue, type EditPrototypeInput, type EditPrototypeValue, type LifecycleResult, type BonfireIdentity, type PrototypeCommandInput, type SavePrototypeValue, type ThemeRegistry } from "@ebay-dt/bonfire-core";
2
2
  import { type PrototypeCatalogLike } from "../prototype-catalog.js";
3
3
  import type { PreparePrototypeFiles } from "../prepare-prototype-files.js";
4
- export interface PrototypeLifecycle {
4
+ import { type DeploymentProvider } from "../deployment-provider.js";
5
+ type LifecycleMethod<Input, Value> = (identity: BonfireIdentity, input: Input) => Promise<LifecycleResult<Value>>;
6
+ export type PrototypeLifecycle = {
5
7
  prepareBranch(identity: BonfireIdentity): Promise<LifecycleResult<BranchPreparationValue>>;
6
- create(identity: BonfireIdentity, input: CreatePrototypeInput): Promise<LifecycleResult<CreatePrototypeValue>>;
7
- duplicate(identity: BonfireIdentity, input: DuplicatePrototypeInput): Promise<LifecycleResult<DuplicatePrototypeValue>>;
8
- getDirtyState(identity: BonfireIdentity, input: PrototypeCommandInput): Promise<LifecycleResult<DirtyObservationValue>>;
9
- save(identity: BonfireIdentity, input: SavePrototypeInput): Promise<LifecycleResult<SavePrototypeValue>>;
10
- discard(identity: BonfireIdentity, input: DiscardPrototypeInput): Promise<LifecycleResult<DiscardPrototypeValue>>;
11
- }
8
+ create: LifecycleMethod<CreatePrototypeInput, CreatePrototypeValue>;
9
+ duplicate: LifecycleMethod<DuplicatePrototypeInput, DuplicatePrototypeValue>;
10
+ edit: LifecycleMethod<EditPrototypeInput, EditPrototypeValue>;
11
+ delete: LifecycleMethod<PrototypeCommandInput, DeletePrototypeValue>;
12
+ getDirtyState: LifecycleMethod<PrototypeCommandInput, DirtyObservationValue>;
13
+ save: LifecycleMethod<PrototypeCommandInput, SavePrototypeValue>;
14
+ discard: LifecycleMethod<PrototypeCommandInput, DiscardPrototypeValue>;
15
+ deploy: LifecycleMethod<PrototypeCommandInput, DeploymentValue>;
16
+ };
12
17
  export type LocalPrototypeLifecycleOptions = {
13
18
  repositoryRoot: string;
14
19
  prototypeRoot?: string;
@@ -21,15 +26,22 @@ export type LocalPrototypeLifecycleOptions = {
21
26
  };
22
27
  catalog?: PrototypeCatalogLike;
23
28
  preparePrototypeFiles?: PreparePrototypeFiles;
29
+ deploymentProvider?: DeploymentProvider;
30
+ applicationId?: string;
31
+ themeRegistry?: ThemeRegistry;
24
32
  };
25
33
  export declare class LocalPrototypeLifecycle implements PrototypeLifecycle {
26
34
  #private;
27
35
  constructor(options: LocalPrototypeLifecycleOptions);
28
36
  prepareBranch(identity: BonfireIdentity): Promise<LifecycleResult<BranchPreparationValue>>;
29
37
  create(identity: BonfireIdentity, input: CreatePrototypeInput): Promise<LifecycleResult<CreatePrototypeValue>>;
38
+ edit(identity: BonfireIdentity, input: EditPrototypeInput): Promise<LifecycleResult<EditPrototypeValue>>;
39
+ delete(identity: BonfireIdentity, input: PrototypeCommandInput): Promise<LifecycleResult<DeletePrototypeValue>>;
30
40
  getDirtyState(identity: BonfireIdentity, input: PrototypeCommandInput): Promise<LifecycleResult<DirtyObservationValue>>;
31
- save(identity: BonfireIdentity, input: SavePrototypeInput): Promise<LifecycleResult<SavePrototypeValue>>;
32
- discard(identity: BonfireIdentity, input: DiscardPrototypeInput): Promise<LifecycleResult<DiscardPrototypeValue>>;
41
+ deploy(identity: BonfireIdentity, input: PrototypeCommandInput): Promise<LifecycleResult<DeploymentValue>>;
42
+ save(identity: BonfireIdentity, input: PrototypeCommandInput): Promise<LifecycleResult<SavePrototypeValue>>;
43
+ discard(identity: BonfireIdentity, input: PrototypeCommandInput): Promise<LifecycleResult<DiscardPrototypeValue>>;
33
44
  duplicate(identity: BonfireIdentity, input: DuplicatePrototypeInput): Promise<LifecycleResult<DuplicatePrototypeValue>>;
34
45
  }
46
+ export {};
35
47
  //# sourceMappingURL=local-prototype-lifecycle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"local-prototype-lifecycle.d.ts","sourceRoot":"","sources":["../../src/lifecycle/local-prototype-lifecycle.ts"],"names":[],"mappings":"AACA,OAAO,EAYL,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAE5B,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAU/B,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,yBAAyB,CAAC;AAMjC,OAAO,KAAK,EACV,qBAAqB,EAEtB,MAAM,+BAA+B,CAAC;AAWvC,MAAM,WAAW,kBAAkB;IACjC,aAAa,CACX,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC,eAAe,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACpD,MAAM,CACJ,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,eAAe,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAClD,SAAS,CACP,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,eAAe,CAAC,uBAAuB,CAAC,CAAC,CAAC;IACrD,aAAa,CACX,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAAC,CAAC;IACnD,IAAI,CACF,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,eAAe,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAChD,OAAO,CACL,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAAC,CAAC;CACpD;AAED,MAAM,MAAM,8BAA8B,GAAG;IAC3C,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qBAAqB,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IAC/C,cAAc,CAAC,EAAE;QACf,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;CAC/C,CAAC;AA8DF,qBAAa,uBAAwB,YAAW,kBAAkB;;IAMhE,YAAY,OAAO,EAAE,8BAA8B,EAUlD;IAwEK,aAAa,CACjB,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC,eAAe,CAAC,sBAAsB,CAAC,CAAC,CAoBlD;IAEK,MAAM,CACV,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,eAAe,CAAC,oBAAoB,CAAC,CAAC,CAwDhD;IAEK,aAAa,CACjB,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAAC,CAQjD;IAEK,IAAI,CACR,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,eAAe,CAAC,kBAAkB,CAAC,CAAC,CAW9C;IAEK,OAAO,CACX,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAAC,CAWjD;IAEK,SAAS,CACb,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,eAAe,CAAC,uBAAuB,CAAC,CAAC,CA6DnD;CACF"}
1
+ {"version":3,"file":"local-prototype-lifecycle.d.ts","sourceRoot":"","sources":["../../src/lifecycle/local-prototype-lifecycle.ts"],"names":[],"mappings":"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","sourcesContent":["import { constants } from \"node:fs\";\nimport { lstat, open, rename, rm, writeFile } from \"node:fs/promises\";\nimport { randomUUID } from \"node:crypto\";\nimport { join, relative, sep } from \"node:path\";\nimport {\n definePrototypeRef,\n getPrototypeUrl,\n LifecycleInputError,\n lifecycleFailure,\n lifecycleSuccess,\n parseCreatePrototypeInput,\n parseDuplicatePrototypeInput,\n parseEditPrototypeInput,\n parseBonfireIdentity,\n parsePrototypeCommandInput,\n parsePrototypeMetadata,\n slugifyPrototypeTitle,\n PrototypeRefError,\n type BranchPreparationValue,\n type CreatePrototypeInput,\n type CreatePrototypeValue,\n type DeletePrototypeValue,\n type DeploymentValue,\n type DeploymentWarningCode,\n type DirtyObservationValue,\n type DiscardPrototypeValue,\n type DuplicatePrototypeInput,\n type DuplicatePrototypeValue,\n type EditPrototypeInput,\n type EditPrototypeValue,\n type LifecycleErrorCode,\n type LifecycleResult,\n type BonfireIdentity,\n type PrototypeCommandInput,\n type SavePrototypeValue,\n type ThemeRegistry,\n} from \"@ebay-dt/bonfire-core\";\nimport {\n commitPath,\n discardPath,\n GitRepositoryError,\n prepareIdentityBranch,\n pushBranch,\n type GitRepository,\n} from \"../git-repository.js\";\nimport { duplicatePrototype, PrototypeDuplicateError } from \"../duplicate.js\";\nimport {\n PrototypeCatalog,\n type PrototypeCatalogLike,\n} from \"../prototype-catalog.js\";\nimport { PrototypeScaffoldError, scaffoldPrototype } from \"../scaffold.js\";\nimport {\n createPrototypeLifecycleRuntime,\n type PrototypeLifecycleRuntimeContext,\n} from \"./runtime-context.js\";\nimport type {\n PreparePrototypeFiles,\n PreparePrototypeFilesRequest,\n} from \"../prepare-prototype-files.js\";\nimport {\n cleanupOwnedDestination,\n validateOwnedDestination,\n type OwnedDestinationReceipt,\n} from \"../internal/owned-destination.js\";\nimport {\n RepositoryBusyError,\n withRepositoryOperation,\n} from \"./operation-gate.js\";\nimport {\n DeploymentProviderError,\n type DeploymentProvider,\n} from \"../deployment-provider.js\";\n\ntype LifecycleMethod<Input, Value> = (\n identity: BonfireIdentity,\n input: Input,\n) => Promise<LifecycleResult<Value>>;\n\nexport type PrototypeLifecycle = {\n prepareBranch(\n identity: BonfireIdentity,\n ): Promise<LifecycleResult<BranchPreparationValue>>;\n create: LifecycleMethod<CreatePrototypeInput, CreatePrototypeValue>;\n duplicate: LifecycleMethod<DuplicatePrototypeInput, DuplicatePrototypeValue>;\n edit: LifecycleMethod<EditPrototypeInput, EditPrototypeValue>;\n delete: LifecycleMethod<PrototypeCommandInput, DeletePrototypeValue>;\n getDirtyState: LifecycleMethod<PrototypeCommandInput, DirtyObservationValue>;\n save: LifecycleMethod<PrototypeCommandInput, SavePrototypeValue>;\n discard: LifecycleMethod<PrototypeCommandInput, DiscardPrototypeValue>;\n deploy: LifecycleMethod<PrototypeCommandInput, DeploymentValue>;\n};\n\nexport type LocalPrototypeLifecycleOptions = {\n repositoryRoot: string;\n prototypeRoot?: string;\n remote?: string;\n primaryBranch?: string;\n automaticBranchSwitch?: \"enabled\" | \"disabled\";\n commitIdentity?: {\n name: string;\n email: string;\n };\n catalog?: PrototypeCatalogLike;\n preparePrototypeFiles?: PreparePrototypeFiles;\n deploymentProvider?: DeploymentProvider;\n applicationId?: string;\n themeRegistry?: ThemeRegistry;\n};\n\ntype InternalLocalPrototypeLifecycleOptions = LocalPrototypeLifecycleOptions & {\n runtime?: PrototypeLifecycleRuntimeContext;\n};\n\nfunction repositoryRelativePath(\n runtime: PrototypeLifecycleRuntimeContext,\n ref: { login: string; slug: string },\n) {\n const absolutePath = join(runtime.prototypeRoot, ref.login, ref.slug);\n return relative(runtime.repositoryRoot, absolutePath).split(sep).join(\"/\");\n}\n\nfunction destinationRef(identity: BonfireIdentity, title: string) {\n const slug = slugifyPrototypeTitle(title);\n if (slug === null)\n throw new PrototypeRefError(\"title\", \"must produce a slug\");\n return definePrototypeRef({ login: identity.login, slug });\n}\n\nclass PrototypeNotFoundError extends Error {}\nclass PrototypeOwnershipError extends Error {}\nclass PrototypeMetadataFileError extends Error {}\nclass DeploymentUnavailableError extends Error {}\n\nfunction metadataPath(\n runtime: PrototypeLifecycleRuntimeContext,\n ref: { login: string; slug: string },\n) {\n return join(runtime.prototypeRoot, ref.login, ref.slug, \"metadata.json\");\n}\n\nasync function assertPrototypeDirectory(\n runtime: PrototypeLifecycleRuntimeContext,\n ref: { login: string; slug: string },\n) {\n const paths = [\n runtime.prototypeRoot,\n join(runtime.prototypeRoot, ref.login),\n join(runtime.prototypeRoot, ref.login, ref.slug),\n ];\n\n try {\n for (const path of paths) {\n const stats = await lstat(path);\n if (stats.isSymbolicLink() || !stats.isDirectory()) {\n throw new PrototypeMetadataFileError();\n }\n }\n } catch (error) {\n if (error instanceof PrototypeMetadataFileError) throw error;\n throw new PrototypeMetadataFileError();\n }\n}\n\nasync function readMetadataDocument(\n runtime: PrototypeLifecycleRuntimeContext,\n ref: { login: string; slug: string },\n themeRegistry?: ThemeRegistry,\n) {\n const path = metadataPath(runtime, ref);\n try {\n const file = await open(\n path,\n constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0),\n );\n try {\n const source = await file.readFile(\"utf8\");\n const document: unknown = JSON.parse(source);\n parsePrototypeMetadata(document, { themeRegistry });\n if (\n typeof document !== \"object\" ||\n document === null ||\n Array.isArray(document)\n ) {\n throw new PrototypeMetadataFileError();\n }\n return { ...(document as Record<string, unknown>) };\n } finally {\n await file.close();\n }\n } catch (error) {\n if (error instanceof PrototypeMetadataFileError) throw error;\n throw new PrototypeMetadataFileError();\n }\n}\n\nasync function removePrototypeDirectory(\n runtime: PrototypeLifecycleRuntimeContext,\n ref: { login: string; slug: string },\n) {\n await assertPrototypeDirectory(runtime, ref);\n try {\n await rm(join(runtime.prototypeRoot, ref.login, ref.slug), {\n force: false,\n recursive: true,\n });\n } catch {\n throw new PrototypeMetadataFileError();\n }\n}\n\nasync function writeMetadataDocument(\n runtime: PrototypeLifecycleRuntimeContext,\n ref: { login: string; slug: string },\n document: Record<string, unknown>,\n) {\n const path = metadataPath(runtime, ref);\n const temporaryPath = `${path}.${randomUUID()}.tmp`;\n\n try {\n const stats = await lstat(path);\n if (stats.isSymbolicLink() || !stats.isFile()) {\n throw new PrototypeMetadataFileError();\n }\n await writeFile(temporaryPath, `${JSON.stringify(document, null, 2)}\\n`, {\n encoding: \"utf8\",\n flag: \"wx\",\n });\n await rename(temporaryPath, path);\n } catch (error) {\n if (error instanceof PrototypeMetadataFileError) throw error;\n throw new PrototypeMetadataFileError();\n } finally {\n await rm(temporaryPath, { force: true }).catch(() => undefined);\n }\n}\n\nasync function mergeIdentityBranchToPrimary(\n repository: GitRepository,\n options: {\n branch: string;\n primaryBranch: string;\n remote: string;\n },\n) {\n if (options.branch === options.primaryBranch) return;\n\n const hasRemote = (await repository.remotes()).some(\n (remote) => remote.name === options.remote,\n );\n if (!hasRemote) return;\n\n await repository.checkout([options.primaryBranch]);\n try {\n await repository.merge([\n options.branch,\n \"--no-ff\",\n \"-m\",\n `merge: ${options.branch}`,\n ]);\n await repository.push(options.remote, options.primaryBranch);\n } finally {\n await repository.checkout([options.branch]);\n }\n}\n\nfunction mapError(error: unknown): LifecycleErrorCode {\n if (error instanceof PrototypeNotFoundError) return \"not-found\";\n if (error instanceof PrototypeOwnershipError) return \"forbidden\";\n if (error instanceof PrototypeMetadataFileError) return \"filesystem-failure\";\n if (error instanceof RepositoryBusyError) return \"repository-busy\";\n if (\n error instanceof PrototypeRefError ||\n error instanceof LifecycleInputError\n ) {\n return \"invalid-input\";\n }\n if (error instanceof GitRepositoryError) {\n if (error.kind === \"timeout\") return \"timeout\";\n if (error.kind === \"conflict\") return \"working-tree-conflict\";\n if (error.kind === \"invalid-configuration\") return \"unexpected-failure\";\n return \"git-failure\";\n }\n if (error instanceof PrototypeScaffoldError) {\n if (error.kind === \"collision\") return \"already-exists\";\n if (error.kind === \"invalid-input\") return \"invalid-input\";\n return \"filesystem-failure\";\n }\n if (error instanceof PrototypeDuplicateError) {\n if (error.kind === \"source-not-found\") return \"not-found\";\n if (error.kind === \"collision\") return \"already-exists\";\n if (error.kind === \"invalid-source\") return \"invalid-input\";\n return \"filesystem-failure\";\n }\n if (error instanceof DeploymentUnavailableError) {\n return \"deployment-unavailable\";\n }\n if (error instanceof DeploymentProviderError) {\n return error.kind === \"timeout\" ? \"timeout\" : \"deployment-failure\";\n }\n return \"unexpected-failure\";\n}\n\nfunction resultFrom<Value>(\n operation: () => Promise<Value>,\n): Promise<LifecycleResult<Value>> {\n return operation()\n .then(lifecycleSuccess)\n .catch((error: unknown) => lifecycleFailure(mapError(error)));\n}\n\nexport class LocalPrototypeLifecycle implements PrototypeLifecycle {\n readonly #runtime: PrototypeLifecycleRuntimeContext;\n readonly #catalog: PrototypeCatalogLike;\n readonly #repository: GitRepository;\n readonly #preparePrototypeFiles: PreparePrototypeFiles;\n readonly #deploymentProvider?: DeploymentProvider;\n readonly #applicationId?: string;\n readonly #themeRegistry?: ThemeRegistry;\n\n constructor(options: LocalPrototypeLifecycleOptions) {\n const internalOptions = options as InternalLocalPrototypeLifecycleOptions;\n this.#runtime =\n internalOptions.runtime ??\n createPrototypeLifecycleRuntime(internalOptions);\n this.#themeRegistry = options.themeRegistry;\n this.#catalog =\n internalOptions.catalog ??\n new PrototypeCatalog({\n ...this.#runtime,\n themeRegistry: this.#themeRegistry,\n });\n this.#repository = this.#runtime.gitRepository;\n this.#preparePrototypeFiles =\n internalOptions.preparePrototypeFiles ?? (() => undefined);\n this.#deploymentProvider = internalOptions.deploymentProvider;\n this.#applicationId = internalOptions.applicationId;\n if (\n this.#deploymentProvider &&\n (typeof this.#applicationId !== \"string\" ||\n this.#applicationId.length === 0)\n ) {\n throw new TypeError(\n \"applicationId must be provided when deploymentProvider is configured\",\n );\n }\n }\n\n async #verifiedIdentity(identity: BonfireIdentity) {\n return parseBonfireIdentity(identity);\n }\n\n async #currentBranch() {\n const branch = await this.#repository.currentBranch();\n if (branch === \"HEAD\" || branch.length === 0) {\n throw new GitRepositoryError(\"conflict\", \"current-branch\");\n }\n return branch;\n }\n\n async #ownedPrototype(\n identity: BonfireIdentity,\n input: PrototypeCommandInput,\n ) {\n const verifiedIdentity = await this.#verifiedIdentity(identity);\n const normalizedInput = parsePrototypeCommandInput(input);\n if (normalizedInput.ref.login !== verifiedIdentity.login) {\n throw new PrototypeOwnershipError();\n }\n const summary = await this.#catalog.getSummary(normalizedInput.ref);\n if (!summary) {\n throw new PrototypeNotFoundError();\n }\n\n return {\n ref: normalizedInput.ref,\n path: repositoryRelativePath(this.#runtime, normalizedInput.ref),\n summary,\n };\n }\n\n async #prepareMutationBranch(login: string) {\n if (this.#runtime.automaticBranchSwitch === \"disabled\") {\n return this.#currentBranch();\n }\n\n await prepareIdentityBranch(this.#repository, {\n identityBranch: login,\n primaryBranch: this.#runtime.primaryBranch,\n remote: this.#runtime.remote,\n });\n return login;\n }\n\n async #prepareDestination(\n receipt: OwnedDestinationReceipt,\n expectedFiles: readonly string[],\n request: PreparePrototypeFilesRequest,\n ) {\n try {\n const frozenRequest = Object.freeze({\n ...request,\n ref: Object.freeze({ ...request.ref }),\n identity: Object.freeze({ ...request.identity }),\n ...(request.operation === \"duplicate\"\n ? { source: Object.freeze({ ...request.source }) }\n : {}),\n }) as PreparePrototypeFilesRequest;\n await this.#preparePrototypeFiles(frozenRequest);\n await validateOwnedDestination(\n receipt,\n expectedFiles,\n request.ref,\n this.#themeRegistry,\n );\n } catch {\n try {\n await cleanupOwnedDestination(receipt);\n } catch {\n // The failure remains a finite filesystem failure without retrying cleanup.\n }\n throw new PrototypeScaffoldError(\"filesystem\");\n }\n }\n\n async prepareBranch(\n identity: BonfireIdentity,\n ): Promise<LifecycleResult<BranchPreparationValue>> {\n return resultFrom(async () => {\n const verifiedIdentity = await this.#verifiedIdentity(identity);\n return withRepositoryOperation(\n this.#runtime.repositoryRealPath,\n async () => {\n if (this.#runtime.automaticBranchSwitch === \"disabled\") {\n await this.#currentBranch();\n return { status: \"disabled\" } satisfies BranchPreparationValue;\n }\n\n await prepareIdentityBranch(this.#repository, {\n identityBranch: verifiedIdentity.login,\n primaryBranch: this.#runtime.primaryBranch,\n remote: this.#runtime.remote,\n });\n return { status: \"ready\" } satisfies BranchPreparationValue;\n },\n );\n });\n }\n\n async create(\n identity: BonfireIdentity,\n input: CreatePrototypeInput,\n ): Promise<LifecycleResult<CreatePrototypeValue>> {\n return resultFrom(async () => {\n const verifiedIdentity = await this.#verifiedIdentity(identity);\n const normalizedInput = parseCreatePrototypeInput(\n input,\n this.#themeRegistry,\n );\n const ref = destinationRef(verifiedIdentity, normalizedInput.title);\n\n return withRepositoryOperation(\n this.#runtime.repositoryRealPath,\n async () => {\n if (await this.#catalog.getSummary(ref)) {\n throw new PrototypeScaffoldError(\"collision\");\n }\n\n const branch = await this.#prepareMutationBranch(\n verifiedIdentity.login,\n );\n const prepared = await scaffoldPrototype({\n paths: this.#runtime,\n ref,\n title: normalizedInput.title,\n description: normalizedInput.description,\n authorName: verifiedIdentity.name,\n theme: normalizedInput.theme,\n themeRegistry: this.#themeRegistry,\n });\n await this.#prepareDestination(\n prepared.receipt,\n prepared.expectedFiles,\n {\n operation: \"create\",\n repositoryRoot: this.#runtime.repositoryRoot,\n prototypeRoot: this.#runtime.prototypeRoot,\n prototypePath: prepared.destination,\n ref,\n identity: verifiedIdentity,\n title: normalizedInput.title,\n description: normalizedInput.description,\n },\n );\n await commitPath(\n this.#repository,\n repositoryRelativePath(this.#runtime, ref),\n `init: ${ref.slug}`,\n );\n const push = await pushBranch(\n this.#repository,\n this.#runtime.remote,\n branch,\n );\n\n return {\n ref,\n url: getPrototypeUrl(ref),\n remoteStatus: push.remoteStatus,\n } satisfies CreatePrototypeValue;\n },\n );\n });\n }\n\n async edit(\n identity: BonfireIdentity,\n input: EditPrototypeInput,\n ): Promise<LifecycleResult<EditPrototypeValue>> {\n return resultFrom(async () => {\n const verifiedIdentity = await this.#verifiedIdentity(identity);\n const normalizedInput = parseEditPrototypeInput(\n input,\n this.#themeRegistry,\n );\n\n return withRepositoryOperation(\n this.#runtime.repositoryRealPath,\n async () => {\n const { ref, path } = await this.#ownedPrototype(verifiedIdentity, {\n ref: normalizedInput.ref,\n });\n const branch = await this.#prepareMutationBranch(\n verifiedIdentity.login,\n );\n await assertPrototypeDirectory(this.#runtime, ref);\n const document = await readMetadataDocument(\n this.#runtime,\n ref,\n this.#themeRegistry,\n );\n const metadata = parsePrototypeMetadata(document, {\n themeRegistry: this.#themeRegistry,\n });\n if (metadata.authorLogin !== ref.login) {\n throw new PrototypeNotFoundError();\n }\n\n await writeMetadataDocument(this.#runtime, ref, {\n ...document,\n title: normalizedInput.title,\n description: normalizedInput.description,\n updatedAt: new Date().toISOString(),\n ...(normalizedInput.theme === undefined\n ? {}\n : { theme: normalizedInput.theme }),\n });\n await commitPath(this.#repository, path, `edit: ${ref.slug}`);\n await pushBranch(this.#repository, this.#runtime.remote, branch);\n\n return { edited: true, ref } satisfies EditPrototypeValue;\n },\n );\n });\n }\n\n async delete(\n identity: BonfireIdentity,\n input: PrototypeCommandInput,\n ): Promise<LifecycleResult<DeletePrototypeValue>> {\n return resultFrom(async () => {\n const verifiedIdentity = await this.#verifiedIdentity(identity);\n const normalizedInput = parsePrototypeCommandInput(input);\n\n return withRepositoryOperation(\n this.#runtime.repositoryRealPath,\n async () => {\n const { ref, path } = await this.#ownedPrototype(\n verifiedIdentity,\n normalizedInput,\n );\n const branch = await this.#prepareMutationBranch(\n verifiedIdentity.login,\n );\n await removePrototypeDirectory(this.#runtime, ref);\n await commitPath(this.#repository, path, `delete: ${ref.slug}`);\n await pushBranch(this.#repository, this.#runtime.remote, branch);\n\n return { deleted: true, ref } satisfies DeletePrototypeValue;\n },\n );\n });\n }\n\n async getDirtyState(\n identity: BonfireIdentity,\n input: PrototypeCommandInput,\n ): Promise<LifecycleResult<DirtyObservationValue>> {\n return resultFrom(async () => {\n const { path, summary } = await this.#ownedPrototype(identity, input);\n const status = await this.#repository.status([path]);\n return {\n status: status.files.length > 0 ? \"dirty\" : \"clean\",\n ...(summary.previewUrl !== undefined\n ? { previewUrl: summary.previewUrl }\n : {}),\n } satisfies DirtyObservationValue;\n });\n }\n\n async deploy(\n identity: BonfireIdentity,\n input: PrototypeCommandInput,\n ): Promise<LifecycleResult<DeploymentValue>> {\n return resultFrom(async () => {\n const verifiedIdentity = await this.#verifiedIdentity(identity);\n const normalizedInput = parsePrototypeCommandInput(input);\n if (!this.#deploymentProvider) {\n throw new DeploymentUnavailableError();\n }\n\n return withRepositoryOperation(\n this.#runtime.repositoryRealPath,\n async () => {\n const { ref, path } = await this.#ownedPrototype(\n verifiedIdentity,\n normalizedInput,\n );\n const branch = await this.#prepareMutationBranch(\n verifiedIdentity.login,\n );\n await assertPrototypeDirectory(this.#runtime, ref);\n const document = await readMetadataDocument(\n this.#runtime,\n ref,\n this.#themeRegistry,\n );\n const metadata = parsePrototypeMetadata(document, {\n themeRegistry: this.#themeRegistry,\n });\n if (metadata.authorLogin !== ref.login) {\n throw new PrototypeNotFoundError();\n }\n await commitPath(this.#repository, path, `deploy: ${ref.slug}`);\n await pushBranch(this.#repository, this.#runtime.remote, branch);\n\n const deployment = await this.#deploymentProvider!.deploy({\n applicationId: this.#applicationId!,\n login: verifiedIdentity.login,\n });\n const previewUrl = new URL(\n getPrototypeUrl(ref).slice(1),\n `${deployment.url.replace(/\\/+$/, \"\")}/`,\n ).toString();\n\n let warning: DeploymentWarningCode | undefined;\n try {\n await writeMetadataDocument(this.#runtime, ref, {\n ...document,\n previewUrl,\n updatedAt: new Date().toISOString(),\n });\n await commitPath(\n this.#repository,\n path,\n `deploy: ${ref.slug} (metadata)`,\n );\n await pushBranch(this.#repository, this.#runtime.remote, branch);\n } catch {\n warning = \"metadata-persistence-failed\";\n }\n\n if (warning === undefined) {\n try {\n await mergeIdentityBranchToPrimary(this.#repository, {\n branch,\n primaryBranch: this.#runtime.primaryBranch,\n remote: this.#runtime.remote,\n });\n } catch {\n warning = \"primary-merge-failed\";\n }\n }\n\n return {\n deployed: true,\n ref,\n previewUrl,\n deploymentId: deployment.deploymentId,\n readyState: deployment.readyState,\n warning,\n } satisfies DeploymentValue;\n },\n );\n });\n }\n\n async save(\n identity: BonfireIdentity,\n input: PrototypeCommandInput,\n ): Promise<LifecycleResult<SavePrototypeValue>> {\n return resultFrom(async () => {\n const { ref, path } = await this.#ownedPrototype(identity, input);\n return withRepositoryOperation(\n this.#runtime.repositoryRealPath,\n async () => {\n await commitPath(this.#repository, path, `save: ${ref.slug}`);\n return { saved: true } satisfies SavePrototypeValue;\n },\n );\n });\n }\n\n async discard(\n identity: BonfireIdentity,\n input: PrototypeCommandInput,\n ): Promise<LifecycleResult<DiscardPrototypeValue>> {\n return resultFrom(async () => {\n const { path } = await this.#ownedPrototype(identity, input);\n return withRepositoryOperation(\n this.#runtime.repositoryRealPath,\n async () => {\n await discardPath(this.#repository, path);\n return { discarded: true } satisfies DiscardPrototypeValue;\n },\n );\n });\n }\n\n async duplicate(\n identity: BonfireIdentity,\n input: DuplicatePrototypeInput,\n ): Promise<LifecycleResult<DuplicatePrototypeValue>> {\n return resultFrom(async () => {\n const verifiedIdentity = await this.#verifiedIdentity(identity);\n const normalizedInput = parseDuplicatePrototypeInput(input);\n if (!(await this.#catalog.getSummary(normalizedInput.source))) {\n throw new PrototypeDuplicateError(\"source-not-found\");\n }\n const ref = destinationRef(verifiedIdentity, normalizedInput.title);\n\n return withRepositoryOperation(\n this.#runtime.repositoryRealPath,\n async () => {\n if (await this.#catalog.getSummary(ref)) {\n throw new PrototypeDuplicateError(\"collision\");\n }\n\n const branch = await this.#prepareMutationBranch(\n verifiedIdentity.login,\n );\n const prepared = await duplicatePrototype({\n paths: this.#runtime,\n source: normalizedInput.source,\n destination: ref,\n title: normalizedInput.title,\n description: normalizedInput.description,\n authorName: verifiedIdentity.name,\n themeRegistry: this.#themeRegistry,\n });\n await this.#prepareDestination(\n prepared.receipt,\n prepared.expectedFiles,\n {\n operation: \"duplicate\",\n repositoryRoot: this.#runtime.repositoryRoot,\n prototypeRoot: this.#runtime.prototypeRoot,\n prototypePath: prepared.destination,\n ref,\n source: normalizedInput.source,\n identity: verifiedIdentity,\n title: normalizedInput.title,\n description: normalizedInput.description,\n },\n );\n await commitPath(\n this.#repository,\n repositoryRelativePath(this.#runtime, ref),\n `init: ${ref.slug}`,\n );\n const push = await pushBranch(\n this.#repository,\n this.#runtime.remote,\n branch,\n );\n\n return {\n ref,\n url: getPrototypeUrl(ref),\n remoteStatus: push.remoteStatus,\n } satisfies DuplicatePrototypeValue;\n },\n );\n });\n }\n}\n"]}
@@ -1,5 +1,8 @@
1
+ import { constants } from "node:fs";
2
+ import { lstat, open, rename, rm, writeFile } from "node:fs/promises";
3
+ import { randomUUID } from "node:crypto";
1
4
  import { join, relative, sep } from "node:path";
2
- import { definePrototypeRef, getPrototypeUrl, LifecycleInputError, lifecycleFailure, lifecycleSuccess, parseCreatePrototypeInput, parseDuplicatePrototypeInput, parseBonfireIdentity, parsePrototypeCommandInput, slugifyPrototypeTitle, PrototypeRefError, } from "@ebay-dt/bonfire-core";
5
+ import { definePrototypeRef, getPrototypeUrl, LifecycleInputError, lifecycleFailure, lifecycleSuccess, parseCreatePrototypeInput, parseDuplicatePrototypeInput, parseEditPrototypeInput, parseBonfireIdentity, parsePrototypeCommandInput, parsePrototypeMetadata, slugifyPrototypeTitle, PrototypeRefError, } from "@ebay-dt/bonfire-core";
3
6
  import { commitPath, discardPath, GitRepositoryError, prepareIdentityBranch, pushBranch, } from "../git-repository.js";
4
7
  import { duplicatePrototype, PrototypeDuplicateError } from "../duplicate.js";
5
8
  import { PrototypeCatalog, } from "../prototype-catalog.js";
@@ -7,6 +10,7 @@ import { PrototypeScaffoldError, scaffoldPrototype } from "../scaffold.js";
7
10
  import { createPrototypeLifecycleRuntime, } from "./runtime-context.js";
8
11
  import { cleanupOwnedDestination, validateOwnedDestination, } from "../internal/owned-destination.js";
9
12
  import { RepositoryBusyError, withRepositoryOperation, } from "./operation-gate.js";
13
+ import { DeploymentProviderError, } from "../deployment-provider.js";
10
14
  function repositoryRelativePath(runtime, ref) {
11
15
  const absolutePath = join(runtime.prototypeRoot, ref.login, ref.slug);
12
16
  return relative(runtime.repositoryRoot, absolutePath).split(sep).join("/");
@@ -21,11 +25,120 @@ class PrototypeNotFoundError extends Error {
21
25
  }
22
26
  class PrototypeOwnershipError extends Error {
23
27
  }
28
+ class PrototypeMetadataFileError extends Error {
29
+ }
30
+ class DeploymentUnavailableError extends Error {
31
+ }
32
+ function metadataPath(runtime, ref) {
33
+ return join(runtime.prototypeRoot, ref.login, ref.slug, "metadata.json");
34
+ }
35
+ async function assertPrototypeDirectory(runtime, ref) {
36
+ const paths = [
37
+ runtime.prototypeRoot,
38
+ join(runtime.prototypeRoot, ref.login),
39
+ join(runtime.prototypeRoot, ref.login, ref.slug),
40
+ ];
41
+ try {
42
+ for (const path of paths) {
43
+ const stats = await lstat(path);
44
+ if (stats.isSymbolicLink() || !stats.isDirectory()) {
45
+ throw new PrototypeMetadataFileError();
46
+ }
47
+ }
48
+ }
49
+ catch (error) {
50
+ if (error instanceof PrototypeMetadataFileError)
51
+ throw error;
52
+ throw new PrototypeMetadataFileError();
53
+ }
54
+ }
55
+ async function readMetadataDocument(runtime, ref, themeRegistry) {
56
+ const path = metadataPath(runtime, ref);
57
+ try {
58
+ const file = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
59
+ try {
60
+ const source = await file.readFile("utf8");
61
+ const document = JSON.parse(source);
62
+ parsePrototypeMetadata(document, { themeRegistry });
63
+ if (typeof document !== "object" ||
64
+ document === null ||
65
+ Array.isArray(document)) {
66
+ throw new PrototypeMetadataFileError();
67
+ }
68
+ return { ...document };
69
+ }
70
+ finally {
71
+ await file.close();
72
+ }
73
+ }
74
+ catch (error) {
75
+ if (error instanceof PrototypeMetadataFileError)
76
+ throw error;
77
+ throw new PrototypeMetadataFileError();
78
+ }
79
+ }
80
+ async function removePrototypeDirectory(runtime, ref) {
81
+ await assertPrototypeDirectory(runtime, ref);
82
+ try {
83
+ await rm(join(runtime.prototypeRoot, ref.login, ref.slug), {
84
+ force: false,
85
+ recursive: true,
86
+ });
87
+ }
88
+ catch {
89
+ throw new PrototypeMetadataFileError();
90
+ }
91
+ }
92
+ async function writeMetadataDocument(runtime, ref, document) {
93
+ const path = metadataPath(runtime, ref);
94
+ const temporaryPath = `${path}.${randomUUID()}.tmp`;
95
+ try {
96
+ const stats = await lstat(path);
97
+ if (stats.isSymbolicLink() || !stats.isFile()) {
98
+ throw new PrototypeMetadataFileError();
99
+ }
100
+ await writeFile(temporaryPath, `${JSON.stringify(document, null, 2)}\n`, {
101
+ encoding: "utf8",
102
+ flag: "wx",
103
+ });
104
+ await rename(temporaryPath, path);
105
+ }
106
+ catch (error) {
107
+ if (error instanceof PrototypeMetadataFileError)
108
+ throw error;
109
+ throw new PrototypeMetadataFileError();
110
+ }
111
+ finally {
112
+ await rm(temporaryPath, { force: true }).catch(() => undefined);
113
+ }
114
+ }
115
+ async function mergeIdentityBranchToPrimary(repository, options) {
116
+ if (options.branch === options.primaryBranch)
117
+ return;
118
+ const hasRemote = (await repository.remotes()).some((remote) => remote.name === options.remote);
119
+ if (!hasRemote)
120
+ return;
121
+ await repository.checkout([options.primaryBranch]);
122
+ try {
123
+ await repository.merge([
124
+ options.branch,
125
+ "--no-ff",
126
+ "-m",
127
+ `merge: ${options.branch}`,
128
+ ]);
129
+ await repository.push(options.remote, options.primaryBranch);
130
+ }
131
+ finally {
132
+ await repository.checkout([options.branch]);
133
+ }
134
+ }
24
135
  function mapError(error) {
25
136
  if (error instanceof PrototypeNotFoundError)
26
137
  return "not-found";
27
138
  if (error instanceof PrototypeOwnershipError)
28
139
  return "forbidden";
140
+ if (error instanceof PrototypeMetadataFileError)
141
+ return "filesystem-failure";
29
142
  if (error instanceof RepositoryBusyError)
30
143
  return "repository-busy";
31
144
  if (error instanceof PrototypeRefError ||
@@ -57,6 +170,12 @@ function mapError(error) {
57
170
  return "invalid-input";
58
171
  return "filesystem-failure";
59
172
  }
173
+ if (error instanceof DeploymentUnavailableError) {
174
+ return "deployment-unavailable";
175
+ }
176
+ if (error instanceof DeploymentProviderError) {
177
+ return error.kind === "timeout" ? "timeout" : "deployment-failure";
178
+ }
60
179
  return "unexpected-failure";
61
180
  }
62
181
  function resultFrom(operation) {
@@ -69,16 +188,31 @@ export class LocalPrototypeLifecycle {
69
188
  #catalog;
70
189
  #repository;
71
190
  #preparePrototypeFiles;
191
+ #deploymentProvider;
192
+ #applicationId;
193
+ #themeRegistry;
72
194
  constructor(options) {
73
195
  const internalOptions = options;
74
196
  this.#runtime =
75
197
  internalOptions.runtime ??
76
198
  createPrototypeLifecycleRuntime(internalOptions);
199
+ this.#themeRegistry = options.themeRegistry;
77
200
  this.#catalog =
78
- internalOptions.catalog ?? new PrototypeCatalog(this.#runtime);
201
+ internalOptions.catalog ??
202
+ new PrototypeCatalog({
203
+ ...this.#runtime,
204
+ themeRegistry: this.#themeRegistry,
205
+ });
79
206
  this.#repository = this.#runtime.gitRepository;
80
207
  this.#preparePrototypeFiles =
81
208
  internalOptions.preparePrototypeFiles ?? (() => undefined);
209
+ this.#deploymentProvider = internalOptions.deploymentProvider;
210
+ this.#applicationId = internalOptions.applicationId;
211
+ if (this.#deploymentProvider &&
212
+ (typeof this.#applicationId !== "string" ||
213
+ this.#applicationId.length === 0)) {
214
+ throw new TypeError("applicationId must be provided when deploymentProvider is configured");
215
+ }
82
216
  }
83
217
  async #verifiedIdentity(identity) {
84
218
  return parseBonfireIdentity(identity);
@@ -96,12 +230,14 @@ export class LocalPrototypeLifecycle {
96
230
  if (normalizedInput.ref.login !== verifiedIdentity.login) {
97
231
  throw new PrototypeOwnershipError();
98
232
  }
99
- if (!(await this.#catalog.getSummary(normalizedInput.ref))) {
233
+ const summary = await this.#catalog.getSummary(normalizedInput.ref);
234
+ if (!summary) {
100
235
  throw new PrototypeNotFoundError();
101
236
  }
102
237
  return {
103
238
  ref: normalizedInput.ref,
104
239
  path: repositoryRelativePath(this.#runtime, normalizedInput.ref),
240
+ summary,
105
241
  };
106
242
  }
107
243
  async #prepareMutationBranch(login) {
@@ -126,7 +262,7 @@ export class LocalPrototypeLifecycle {
126
262
  : {}),
127
263
  });
128
264
  await this.#preparePrototypeFiles(frozenRequest);
129
- await validateOwnedDestination(receipt, expectedFiles, request.ref);
265
+ await validateOwnedDestination(receipt, expectedFiles, request.ref, this.#themeRegistry);
130
266
  }
131
267
  catch {
132
268
  try {
@@ -158,7 +294,7 @@ export class LocalPrototypeLifecycle {
158
294
  async create(identity, input) {
159
295
  return resultFrom(async () => {
160
296
  const verifiedIdentity = await this.#verifiedIdentity(identity);
161
- const normalizedInput = parseCreatePrototypeInput(input);
297
+ const normalizedInput = parseCreatePrototypeInput(input, this.#themeRegistry);
162
298
  const ref = destinationRef(verifiedIdentity, normalizedInput.title);
163
299
  return withRepositoryOperation(this.#runtime.repositoryRealPath, async () => {
164
300
  if (await this.#catalog.getSummary(ref)) {
@@ -171,6 +307,8 @@ export class LocalPrototypeLifecycle {
171
307
  title: normalizedInput.title,
172
308
  description: normalizedInput.description,
173
309
  authorName: verifiedIdentity.name,
310
+ theme: normalizedInput.theme,
311
+ themeRegistry: this.#themeRegistry,
174
312
  });
175
313
  await this.#prepareDestination(prepared.receipt, prepared.expectedFiles, {
176
314
  operation: "create",
@@ -192,15 +330,125 @@ export class LocalPrototypeLifecycle {
192
330
  });
193
331
  });
194
332
  }
333
+ async edit(identity, input) {
334
+ return resultFrom(async () => {
335
+ const verifiedIdentity = await this.#verifiedIdentity(identity);
336
+ const normalizedInput = parseEditPrototypeInput(input, this.#themeRegistry);
337
+ return withRepositoryOperation(this.#runtime.repositoryRealPath, async () => {
338
+ const { ref, path } = await this.#ownedPrototype(verifiedIdentity, {
339
+ ref: normalizedInput.ref,
340
+ });
341
+ const branch = await this.#prepareMutationBranch(verifiedIdentity.login);
342
+ await assertPrototypeDirectory(this.#runtime, ref);
343
+ const document = await readMetadataDocument(this.#runtime, ref, this.#themeRegistry);
344
+ const metadata = parsePrototypeMetadata(document, {
345
+ themeRegistry: this.#themeRegistry,
346
+ });
347
+ if (metadata.authorLogin !== ref.login) {
348
+ throw new PrototypeNotFoundError();
349
+ }
350
+ await writeMetadataDocument(this.#runtime, ref, {
351
+ ...document,
352
+ title: normalizedInput.title,
353
+ description: normalizedInput.description,
354
+ updatedAt: new Date().toISOString(),
355
+ ...(normalizedInput.theme === undefined
356
+ ? {}
357
+ : { theme: normalizedInput.theme }),
358
+ });
359
+ await commitPath(this.#repository, path, `edit: ${ref.slug}`);
360
+ await pushBranch(this.#repository, this.#runtime.remote, branch);
361
+ return { edited: true, ref };
362
+ });
363
+ });
364
+ }
365
+ async delete(identity, input) {
366
+ return resultFrom(async () => {
367
+ const verifiedIdentity = await this.#verifiedIdentity(identity);
368
+ const normalizedInput = parsePrototypeCommandInput(input);
369
+ return withRepositoryOperation(this.#runtime.repositoryRealPath, async () => {
370
+ const { ref, path } = await this.#ownedPrototype(verifiedIdentity, normalizedInput);
371
+ const branch = await this.#prepareMutationBranch(verifiedIdentity.login);
372
+ await removePrototypeDirectory(this.#runtime, ref);
373
+ await commitPath(this.#repository, path, `delete: ${ref.slug}`);
374
+ await pushBranch(this.#repository, this.#runtime.remote, branch);
375
+ return { deleted: true, ref };
376
+ });
377
+ });
378
+ }
195
379
  async getDirtyState(identity, input) {
196
380
  return resultFrom(async () => {
197
- const { path } = await this.#ownedPrototype(identity, input);
381
+ const { path, summary } = await this.#ownedPrototype(identity, input);
198
382
  const status = await this.#repository.status([path]);
199
383
  return {
200
384
  status: status.files.length > 0 ? "dirty" : "clean",
385
+ ...(summary.previewUrl !== undefined
386
+ ? { previewUrl: summary.previewUrl }
387
+ : {}),
201
388
  };
202
389
  });
203
390
  }
391
+ async deploy(identity, input) {
392
+ return resultFrom(async () => {
393
+ const verifiedIdentity = await this.#verifiedIdentity(identity);
394
+ const normalizedInput = parsePrototypeCommandInput(input);
395
+ if (!this.#deploymentProvider) {
396
+ throw new DeploymentUnavailableError();
397
+ }
398
+ return withRepositoryOperation(this.#runtime.repositoryRealPath, async () => {
399
+ const { ref, path } = await this.#ownedPrototype(verifiedIdentity, normalizedInput);
400
+ const branch = await this.#prepareMutationBranch(verifiedIdentity.login);
401
+ await assertPrototypeDirectory(this.#runtime, ref);
402
+ const document = await readMetadataDocument(this.#runtime, ref, this.#themeRegistry);
403
+ const metadata = parsePrototypeMetadata(document, {
404
+ themeRegistry: this.#themeRegistry,
405
+ });
406
+ if (metadata.authorLogin !== ref.login) {
407
+ throw new PrototypeNotFoundError();
408
+ }
409
+ await commitPath(this.#repository, path, `deploy: ${ref.slug}`);
410
+ await pushBranch(this.#repository, this.#runtime.remote, branch);
411
+ const deployment = await this.#deploymentProvider.deploy({
412
+ applicationId: this.#applicationId,
413
+ login: verifiedIdentity.login,
414
+ });
415
+ const previewUrl = new URL(getPrototypeUrl(ref).slice(1), `${deployment.url.replace(/\/+$/, "")}/`).toString();
416
+ let warning;
417
+ try {
418
+ await writeMetadataDocument(this.#runtime, ref, {
419
+ ...document,
420
+ previewUrl,
421
+ updatedAt: new Date().toISOString(),
422
+ });
423
+ await commitPath(this.#repository, path, `deploy: ${ref.slug} (metadata)`);
424
+ await pushBranch(this.#repository, this.#runtime.remote, branch);
425
+ }
426
+ catch {
427
+ warning = "metadata-persistence-failed";
428
+ }
429
+ if (warning === undefined) {
430
+ try {
431
+ await mergeIdentityBranchToPrimary(this.#repository, {
432
+ branch,
433
+ primaryBranch: this.#runtime.primaryBranch,
434
+ remote: this.#runtime.remote,
435
+ });
436
+ }
437
+ catch {
438
+ warning = "primary-merge-failed";
439
+ }
440
+ }
441
+ return {
442
+ deployed: true,
443
+ ref,
444
+ previewUrl,
445
+ deploymentId: deployment.deploymentId,
446
+ readyState: deployment.readyState,
447
+ warning,
448
+ };
449
+ });
450
+ });
451
+ }
204
452
  async save(identity, input) {
205
453
  return resultFrom(async () => {
206
454
  const { ref, path } = await this.#ownedPrototype(identity, input);
@@ -239,6 +487,7 @@ export class LocalPrototypeLifecycle {
239
487
  title: normalizedInput.title,
240
488
  description: normalizedInput.description,
241
489
  authorName: verifiedIdentity.name,
490
+ themeRegistry: this.#themeRegistry,
242
491
  });
243
492
  await this.#prepareDestination(prepared.receipt, prepared.expectedFiles, {
244
493
  operation: "duplicate",