@czfabrics/front-ready 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.dist/main.mjs ADDED
@@ -0,0 +1,1337 @@
1
+ #!/usr/bin/env node
2
+ // src/config/schema.ts
3
+ import z from "zod";
4
+ var NUMERIC = /* @__PURE__ */ new Set([
5
+ "max-age",
6
+ "s-maxage",
7
+ "stale-while-revalidate",
8
+ "stale-if-error"
9
+ ]);
10
+ var FLAGS = /* @__PURE__ */ new Set([
11
+ "no-cache",
12
+ "no-store",
13
+ "no-transform",
14
+ "must-revalidate",
15
+ "proxy-revalidate",
16
+ "must-understand",
17
+ "private",
18
+ "public",
19
+ "immutable"
20
+ ]);
21
+ var CacheControlSchema = z.string().superRefine((header, ctx) => {
22
+ for (const part of header.split(",")) {
23
+ const t = part.trim();
24
+ if (!t) continue;
25
+ const i = t.indexOf("=");
26
+ const name = (i === -1 ? t : t.slice(0, i)).trim().toLowerCase();
27
+ const value = i === -1 ? void 0 : t.slice(i + 1).trim().replace(/^"(.*)"$/, "$1");
28
+ if (NUMERIC.has(name)) {
29
+ if (value === void 0 || !/^\d+$/.test(value)) {
30
+ ctx.addIssue({
31
+ code: "custom",
32
+ message: `Directive "${name}" requires a non-negative integer`
33
+ });
34
+ }
35
+ } else if (FLAGS.has(name)) {
36
+ if (value !== void 0) {
37
+ ctx.addIssue({
38
+ code: "custom",
39
+ message: `Directive "${name}" does not take a value`
40
+ });
41
+ }
42
+ } else {
43
+ ctx.addIssue({
44
+ code: "custom",
45
+ message: `Unknown directive "${name}"`
46
+ });
47
+ }
48
+ }
49
+ });
50
+ var ConfigSchema = z.object({
51
+ bucket: z.object({
52
+ namePrefix: z.string().nonempty().regex(/[a-z-]*/, "String should be kekab-case string"),
53
+ params: z.object({
54
+ region: z.string().nonempty(),
55
+ apiVersion: z.string().nonempty(),
56
+ endpoint: z.string().nonempty(),
57
+ forcePathStyle: z.union([z.stringbool(), z.boolean()]).optional(),
58
+ credentials: z.object({
59
+ accessKeyId: z.string().nonempty(),
60
+ secretAccessKey: z.string().nonempty()
61
+ })
62
+ }),
63
+ front: z.object({
64
+ cacheControlMapping: z.record(
65
+ z.templateLiteral([z.literal("^"), z.string(), z.literal("$")]),
66
+ CacheControlSchema
67
+ ).default({
68
+ "^index.html$": "max-age=60, stale-while-revalidate=600, stale-if-error=86400",
69
+ "^assets/.+$": "max-age=86400, stale-while-revalidate=600, stale-if-error=86400",
70
+ "^translate/.+$": "max-age=14400, stale-while-revalidate=600, stale-if-error=86400",
71
+ "^.+$": "max-age=31536000, stale-while-revalidate=600, stale-if-error=86400"
72
+ }),
73
+ indexDocumentSuffix: z.string().nonempty().default("index.html"),
74
+ errorDocumentKey: z.string().nonempty().default("index.html")
75
+ }).prefault({}),
76
+ upload: z.object({
77
+ concurrency: z.number().positive().default(50)
78
+ }).prefault({})
79
+ }),
80
+ front: z.discriminatedUnion("type", [
81
+ z.object({
82
+ type: z.literal("angular"),
83
+ angular: z.object({
84
+ projectName: z.string().nonempty(),
85
+ angularJsonPath: z.string().nonempty(),
86
+ configurationName: z.string().nonempty().optional()
87
+ })
88
+ }),
89
+ z.object({
90
+ type: z.literal("custom"),
91
+ custom: z.object({
92
+ build: z.object({
93
+ command: z.string().nonempty(),
94
+ args: z.array(z.string().nonempty())
95
+ }),
96
+ environmentName: z.string().nonempty(),
97
+ buildOutputPath: z.string().nonempty()
98
+ })
99
+ })
100
+ ])
101
+ });
102
+
103
+ // src/errors/config_format.ts
104
+ import { Data } from "effect";
105
+ import z2 from "zod";
106
+ var ConfigFormatError = class extends Data.TaggedError("ConfigFormatError") {
107
+ get message() {
108
+ return z2.prettifyError(this.cause);
109
+ }
110
+ };
111
+
112
+ // src/errors/interop/config_wrapper.ts
113
+ import { Data as Data2 } from "effect";
114
+ var ConfigWrapperError = class extends Data2.TaggedError("ConfigWrapperError") {
115
+ };
116
+
117
+ // src/helpers/effect.ts
118
+ import { Effect } from "effect";
119
+ var toEffectSync = function(fn, errorClass, additionalData) {
120
+ return toEffect(
121
+ new Promise((resolve) => {
122
+ resolve(fn());
123
+ }),
124
+ errorClass,
125
+ additionalData
126
+ );
127
+ };
128
+ var toEffect = function(promise, errorClass, additionalData) {
129
+ return Effect.mapError(
130
+ Effect.tryPromise(() => promise),
131
+ (error) => new errorClass({
132
+ message: error.message,
133
+ cause: error,
134
+ ...additionalData
135
+ })
136
+ );
137
+ };
138
+ var genFn = function(callback) {
139
+ return (...args) => {
140
+ return Effect.gen(() => callback(...args));
141
+ };
142
+ };
143
+
144
+ // src/config/loader.ts
145
+ import { loadConfig as c12LoadConfig } from "c12";
146
+ import { Effect as Effect2 } from "effect";
147
+ var loadConfig = genFn(function* () {
148
+ const { config } = yield* toEffect(
149
+ c12LoadConfig({
150
+ name: "frontready",
151
+ configFileRequired: true
152
+ }),
153
+ ConfigWrapperError,
154
+ {}
155
+ );
156
+ const parsedConfig = ConfigSchema.safeParse(config);
157
+ if (parsedConfig.success) {
158
+ return parsedConfig.data;
159
+ }
160
+ return yield* Effect2.fail(
161
+ new ConfigFormatError({
162
+ cause: parsedConfig.error
163
+ })
164
+ );
165
+ });
166
+
167
+ // src/contexts/cli_command.ts
168
+ import { Context } from "effect";
169
+ var CliCommandContext = class extends Context.Tag("CliCommandContext")() {
170
+ };
171
+
172
+ // src/cli/cli_starter.ts
173
+ import { intro, log } from "@clack/prompts";
174
+ import { Effect as Effect3 } from "effect";
175
+
176
+ // package.json
177
+ var package_default = {
178
+ name: "@czfabrics/front-ready",
179
+ version: "0.1.0-beta.1",
180
+ author: {
181
+ name: "Dylan Valentin",
182
+ email: "dylan.valentin@ik.me",
183
+ url: "https://github.com/czyrok"
184
+ },
185
+ repository: {
186
+ type: "git",
187
+ url: "git+https://github.com/czfabrics/front-ready.git"
188
+ },
189
+ dependencies: {
190
+ "@aws-sdk/client-s3": "^3.1061.0",
191
+ "@clack/prompts": "^1.5.1",
192
+ "@effect/platform": "^0.96.1",
193
+ "@effect/platform-node": "^0.107.0",
194
+ c12: "^4.0.0-beta.5",
195
+ "cmd-ts": "^0.15.0",
196
+ effect: "^3.21.2",
197
+ mrmime: "^2.0.1",
198
+ zod: "^4.4.3"
199
+ },
200
+ devDependencies: {
201
+ "@appnest/readme": "^1.2.7",
202
+ "@types/pluralize": "^0.0.33",
203
+ "@typescript/analyze-trace": "^0.10.1",
204
+ esbuild: "^0.25.9",
205
+ prettier: "^3.6.2",
206
+ rimraf: "^6.0.1",
207
+ "tsc-alias": "^1.8.16",
208
+ tsx: "^4.21.0",
209
+ "type-fest": "^5.3.1",
210
+ typescript: "^5.9.2",
211
+ "vite-tsconfig-paths": "^6.1.0",
212
+ vitest: "^4.0.18",
213
+ "vscode-generate-index-standalone": "^1.7.1"
214
+ },
215
+ main: "./.dist/index.cjs",
216
+ module: "./.dist/index.mjs",
217
+ types: "./.dist/index.d.ts",
218
+ exports: {
219
+ "./package.json": "./package.json",
220
+ ".": {
221
+ types: "./.dist/index.d.ts",
222
+ import: "./.dist/index.mjs",
223
+ require: "./.dist/index.cjs"
224
+ }
225
+ },
226
+ bin: "./.dist/main.mjs",
227
+ bugs: {
228
+ url: "https://github.com/czfabrics/front-ready/issues"
229
+ },
230
+ description: "A framework-aware deployment tool for static frontends. It reads your project's build configuration (e.g. angular.json), runs the build with the configuration you choose, and pushes the compiled output to an S3 bucket \u2014 with correct content types and cache headers \u2014 so a single command takes you from source to a live, hosted site.",
231
+ files: [
232
+ "package.json",
233
+ "README.md",
234
+ "LICENSE",
235
+ ".dist/**/*"
236
+ ],
237
+ homepage: "https://github.com/czfabrics/front-ready#readme",
238
+ keywords: [
239
+ "cli",
240
+ "deploy",
241
+ "deployment",
242
+ "s3",
243
+ "aws",
244
+ "aws-s3",
245
+ "bucket",
246
+ "frontend",
247
+ "static-site",
248
+ "static-hosting",
249
+ "spa",
250
+ "build",
251
+ "angular",
252
+ "ci-cd",
253
+ "upload",
254
+ "typescript"
255
+ ],
256
+ license: "MIT",
257
+ scripts: {
258
+ "format:all": "prettier . --write",
259
+ typecheck: "tsc --project ./tsconfig.typecheck.json",
260
+ dev: "tsx main.ts",
261
+ test: "vitest",
262
+ "test:coverage": "vitest --coverage.enabled",
263
+ "check:type-perf": "rimraf .tsc_traces && tsc --project ./tsconfig.typecheck.json --generateTrace .tsc_traces && analyze-trace .tsc_traces",
264
+ "prepare:index": "vscode-generate-index-standalone index.ts",
265
+ "prepare:dist": "tsx build.ts",
266
+ "prepare:declaration": "tsc --project ./tsconfig.declaration.json",
267
+ "prepare:imports": "tsc-alias --project tsconfig.declaration.json --outDir .dist",
268
+ build: "rimraf .dist && bun run prepare:index && bun run prepare:dist && bun run prepare:declaration && bun run prepare:imports",
269
+ "generate:readme": "readme generate --config .blueprint.json",
270
+ prepublishOnly: "bun run generate:readme && bun run build"
271
+ }
272
+ };
273
+
274
+ // src/cli/cli_starter.ts
275
+ var startCli = function() {
276
+ return Effect3.gen(function* () {
277
+ const context = yield* CliCommandContext;
278
+ intro(`${package_default.name}@${package_default.version} - ${context.commandName}`);
279
+ const config = yield* loadConfig();
280
+ log.info("Configuration loaded");
281
+ return { config };
282
+ });
283
+ };
284
+
285
+ // src/contexts/internal_config.ts
286
+ import { Context as Context2 } from "effect";
287
+ var InternalConfigContext = class extends Context2.Tag("InternalConfigContext")() {
288
+ };
289
+
290
+ // src/contexts/front_deployment.ts
291
+ import { Context as Context3 } from "effect";
292
+ var FrontDeploymentContext = class extends Context3.Tag("FrontDeploymentContext")() {
293
+ };
294
+
295
+ // src/errors/interop/tui_wrapper.ts
296
+ import { Data as Data3 } from "effect";
297
+ var TUiWrapperError = class extends Data3.TaggedError("TUiWrapperError") {
298
+ };
299
+
300
+ // src/factories/bucket_name.ts
301
+ var makeDeploymentBucketName = function(config, bucketIdentifier) {
302
+ return `${config.bucket.namePrefix}-${bucketIdentifier}`;
303
+ };
304
+
305
+ // src/errors/angular.ts
306
+ import { Data as Data4 } from "effect";
307
+ import z3 from "zod";
308
+ var AngularJsonFormatError = class extends Data4.TaggedError("AngularJsonFormatError") {
309
+ get message() {
310
+ return z3.prettifyError(this.cause);
311
+ }
312
+ };
313
+ var AngularJsonMissingDataError = class extends Data4.TaggedError(
314
+ "AngularJsonMissingDataError"
315
+ ) {
316
+ get message() {
317
+ return `Unable to resolve '${this.subject}' for '${this.projectName}'`;
318
+ }
319
+ };
320
+
321
+ // src/helpers/angular.ts
322
+ import { FileSystem } from "@effect/platform";
323
+ import { Effect as Effect4 } from "effect";
324
+ import z4 from "zod";
325
+ var OutputPathSchema = z4.union([
326
+ z4.string(),
327
+ z4.object({
328
+ base: z4.string(),
329
+ browser: z4.string().optional()
330
+ })
331
+ ]);
332
+ var AngularTargetSchema = z4.object({
333
+ builder: z4.string().optional(),
334
+ options: z4.object({
335
+ outputPath: OutputPathSchema.optional()
336
+ }).optional(),
337
+ configurations: z4.record(z4.string(), z4.unknown()).optional()
338
+ });
339
+ var AngularProjectSchema = z4.object({
340
+ architect: z4.record(z4.string(), AngularTargetSchema).optional(),
341
+ targets: z4.record(z4.string(), AngularTargetSchema).optional()
342
+ });
343
+ var AngularJsonSchema = z4.object({
344
+ projects: z4.record(z4.string(), AngularProjectSchema).optional()
345
+ });
346
+ var getProjectTargets = (project) => {
347
+ return project.architect ?? project.targets ?? {};
348
+ };
349
+ var getAngularConfigurationNames = (angularJson) => {
350
+ const configurationNames = /* @__PURE__ */ new Set();
351
+ for (const project of Object.values(angularJson.projects ?? {})) {
352
+ for (const target of Object.values(getProjectTargets(project))) {
353
+ for (const name of Object.keys(target.configurations ?? {})) {
354
+ configurationNames.add(name);
355
+ }
356
+ }
357
+ }
358
+ return [...configurationNames];
359
+ };
360
+ var usesBrowserSubfolder = (builder) => builder?.endsWith(":application") ?? false;
361
+ var getAngularOutputPath = (angularJson, projectName, targetName = "build") => {
362
+ const projects = angularJson.projects ?? {};
363
+ const project = projects[projectName];
364
+ if (!project) return void 0;
365
+ const target = getProjectTargets(project)[targetName];
366
+ const outputPath = target?.options?.outputPath;
367
+ if (outputPath === void 0) return void 0;
368
+ if (typeof outputPath === "object") {
369
+ const browser = outputPath.browser ?? "browser";
370
+ return browser ? `${outputPath.base}/${browser}` : outputPath.base;
371
+ }
372
+ return usesBrowserSubfolder(target?.builder) ? `${outputPath}/browser` : outputPath;
373
+ };
374
+ var resolveAngularConfigurations = function(angularJsonPath, projectName) {
375
+ return Effect4.gen(function* () {
376
+ const fs = yield* FileSystem.FileSystem;
377
+ const content = yield* fs.readFileString(angularJsonPath);
378
+ const angularJson = yield* Effect4.try(() => JSON.parse(content));
379
+ const parsedJson = AngularJsonSchema.safeParse(angularJson);
380
+ if (!parsedJson.success) {
381
+ return yield* Effect4.fail(
382
+ new AngularJsonFormatError({
383
+ cause: parsedJson.error
384
+ })
385
+ );
386
+ }
387
+ const outputPath = getAngularOutputPath(parsedJson.data, projectName);
388
+ if (!outputPath) {
389
+ return yield* Effect4.fail(
390
+ new AngularJsonMissingDataError({
391
+ subject: "output path",
392
+ projectName
393
+ })
394
+ );
395
+ }
396
+ return {
397
+ configurations: getAngularConfigurationNames(parsedJson.data),
398
+ outputPath
399
+ };
400
+ });
401
+ };
402
+
403
+ // src/factories/front_deployment_context.ts
404
+ import { log as log2, select } from "@clack/prompts";
405
+ import { Command } from "@effect/platform";
406
+ import { Effect as Effect5, Layer, Match } from "effect";
407
+ var makeFrontDeploymentContextLayer = function(rootConfig) {
408
+ return Layer.effect(
409
+ FrontDeploymentContext,
410
+ Match.value(rootConfig.front).pipe(
411
+ Match.when(
412
+ { type: "angular" },
413
+ (config) => Effect5.gen(function* () {
414
+ const { configurations, outputPath } = yield* resolveAngularConfigurations(
415
+ config.angular.angularJsonPath,
416
+ config.angular.projectName
417
+ );
418
+ if (config.angular.configurationName) {
419
+ return {
420
+ command: Command.make("ng", "build", config.angular.configurationName),
421
+ bucketName: makeDeploymentBucketName(
422
+ rootConfig,
423
+ config.angular.configurationName
424
+ ),
425
+ buildOutputPath: outputPath
426
+ };
427
+ }
428
+ log2.warning("Angular configuration name not found in configuration");
429
+ log2.message(
430
+ `Reading '${config.angular.angularJsonPath}' file to get available configurations`
431
+ );
432
+ const options = configurations.map((name) => ({
433
+ value: name,
434
+ label: name
435
+ }));
436
+ const configurationName = yield* toEffect(
437
+ select({
438
+ message: "Pick an Angular configuration.",
439
+ options
440
+ }),
441
+ TUiWrapperError,
442
+ {
443
+ uiFunction: "select"
444
+ }
445
+ );
446
+ return {
447
+ command: Command.make("ng", "build", configurationName.toString()),
448
+ bucketName: makeDeploymentBucketName(
449
+ rootConfig,
450
+ configurationName.toString()
451
+ ),
452
+ buildOutputPath: outputPath
453
+ };
454
+ })
455
+ ),
456
+ Match.when(
457
+ { type: "custom" },
458
+ (config) => Effect5.gen(function* () {
459
+ return {
460
+ command: Command.make(
461
+ config.custom.build.command,
462
+ ...config.custom.build.args
463
+ ),
464
+ bucketName: makeDeploymentBucketName(
465
+ rootConfig,
466
+ config.custom.environmentName
467
+ ),
468
+ buildOutputPath: config.custom.buildOutputPath
469
+ };
470
+ })
471
+ ),
472
+ Match.exhaustive
473
+ )
474
+ );
475
+ };
476
+
477
+ // src/helpers/runtime.ts
478
+ import { Effect as Effect6 } from "effect";
479
+ import * as readline from "node:readline";
480
+ var interruptOnCtrlC = function() {
481
+ return Effect6.async((resume) => {
482
+ readline.emitKeypressEvents(process.stdin);
483
+ if (process.stdin.isTTY) process.stdin.setRawMode(true);
484
+ const onKeypress = (_, key) => {
485
+ if (key.ctrl && key.name === "c") {
486
+ resume(Effect6.interrupt);
487
+ }
488
+ };
489
+ process.stdin.on("keypress", onKeypress);
490
+ return Effect6.sync(() => {
491
+ process.stdin.off("keypress", onKeypress);
492
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
493
+ });
494
+ });
495
+ };
496
+ var runAndInterruptOnCtrlC = function(effect) {
497
+ return Effect6.raceFirst(interruptOnCtrlC(), effect);
498
+ };
499
+ var overrideProcessExit = (override) => Effect6.acquireRelease(
500
+ Effect6.sync(() => {
501
+ const original = process.exit;
502
+ process.exit = override;
503
+ return original;
504
+ }),
505
+ (original) => Effect6.sync(() => {
506
+ process.exit = original;
507
+ })
508
+ );
509
+ var interceptProcessExit = function(effect, callback) {
510
+ return Effect6.scoped(
511
+ Effect6.gen(function* () {
512
+ yield* overrideProcessExit((exitCode) => {
513
+ callback(exitCode ?? 130);
514
+ });
515
+ return yield* effect;
516
+ })
517
+ );
518
+ };
519
+
520
+ // src/contexts/file_object_bucket.ts
521
+ import { Context as Context4 } from "effect";
522
+ var FileObjectBucketContext = class extends Context4.Tag("FileObjectBucketContext")() {
523
+ };
524
+
525
+ // src/contexts/file_repository.ts
526
+ import { Context as Context5 } from "effect";
527
+ var FileRepositoryContext = class extends Context5.Tag("FileRepositoryContext")() {
528
+ };
529
+
530
+ // src/errors/file.ts
531
+ import { Data as Data5 } from "effect";
532
+ var FileError = class extends Data5.TaggedError("FileError") {
533
+ };
534
+ var FileNotFoundError = class extends Data5.TaggedError("FileNotFoundError") {
535
+ };
536
+
537
+ // src/errors/interop/file_type_wrapper.ts
538
+ import { Data as Data6 } from "effect";
539
+ var FileTypeWrapperError = class extends Data6.TaggedError("FileTypeWrapperError") {
540
+ };
541
+
542
+ // src/file/types.ts
543
+ import { Path } from "@effect/platform";
544
+ import { FileSystem as FileSystem2 } from "@effect/platform/FileSystem";
545
+ import { Effect as Effect7, Equal, Hash } from "effect";
546
+ import { lookup } from "mrmime";
547
+ var FileItem = {
548
+ new: function(data) {
549
+ return Effect7.gen(function* () {
550
+ const path = yield* Path.Path;
551
+ const fs = yield* FileSystem2;
552
+ const name = path.basename(data.relativePath);
553
+ const extension = path.extname(data.relativePath);
554
+ const absolutePath = path.resolve(data.cwd, data.relativePath);
555
+ const content = fs.readFile(absolutePath);
556
+ return {
557
+ name,
558
+ extension,
559
+ relativePath: data.relativePath,
560
+ absolutePath,
561
+ cwd: data.cwd,
562
+ content,
563
+ get contentType() {
564
+ return toEffectSync(
565
+ () => lookup(extension) ?? "application/octet-stream",
566
+ FileTypeWrapperError,
567
+ {
568
+ file: this
569
+ }
570
+ );
571
+ },
572
+ get length() {
573
+ return 1;
574
+ },
575
+ [Symbol.iterator]() {
576
+ let isFirst = true;
577
+ return {
578
+ next: () => {
579
+ if (isFirst) {
580
+ isFirst = false;
581
+ return { value: this, done: false };
582
+ }
583
+ return { value: void 0, done: true };
584
+ }
585
+ };
586
+ }
587
+ };
588
+ });
589
+ }
590
+ };
591
+ var FileTreeTypeId = Symbol.for("FileTree");
592
+ var isFileTree = function(thing) {
593
+ return typeof thing === "object" && thing !== null && FileTreeTypeId in thing;
594
+ };
595
+ var FileTree = {
596
+ new: function(data) {
597
+ return Effect7.gen(function* () {
598
+ const path = yield* Path.Path;
599
+ return {
600
+ [FileTreeTypeId]: FileTreeTypeId,
601
+ name: path.basename(data.relativePath),
602
+ relativePath: data.relativePath,
603
+ absolutePath: path.resolve(data.cwd, data.relativePath),
604
+ cwd: data.cwd,
605
+ items: data.items,
606
+ get length() {
607
+ return this.items.length;
608
+ },
609
+ append: function(newItems) {
610
+ return FileTree.new({
611
+ ...this,
612
+ items: [...this.items, ...newItems]
613
+ });
614
+ },
615
+ [Symbol.iterator]() {
616
+ let currentIndex = 0;
617
+ return {
618
+ next: () => {
619
+ if (currentIndex < this.items.length) {
620
+ const currentItem = this.items[currentIndex];
621
+ currentIndex++;
622
+ return { value: currentItem, done: false };
623
+ }
624
+ return { value: void 0, done: true };
625
+ }
626
+ };
627
+ },
628
+ [Equal.symbol](that) {
629
+ return isFileTree(that) && this.absolutePath === that.absolutePath;
630
+ },
631
+ [Hash.symbol]() {
632
+ return Hash.string(this.absolutePath);
633
+ }
634
+ };
635
+ });
636
+ },
637
+ is: function(thing) {
638
+ return isFileTree(thing);
639
+ }
640
+ };
641
+
642
+ // src/file/repository.ts
643
+ import { Path as Path2 } from "@effect/platform";
644
+ import { FileSystem as FileSystem3 } from "@effect/platform/FileSystem";
645
+ import { Effect as Effect8 } from "effect";
646
+ var FileRepository = class extends Effect8.Service()("FileRepository", {
647
+ effect: Effect8.gen(function* () {
648
+ const fs = yield* FileSystem3;
649
+ const path = yield* Path2.Path;
650
+ const context = yield* FileRepositoryContext;
651
+ return {
652
+ listFiles: () => Effect8.gen(function* () {
653
+ const paths = yield* fs.readDirectory(context.cwd, {
654
+ recursive: true
655
+ });
656
+ const filePaths = yield* Effect8.filter(
657
+ paths,
658
+ (relativePath) => fs.stat(path.resolve(context.cwd, relativePath)).pipe(Effect8.map((info) => info.type === "File")),
659
+ { concurrency: "unbounded" }
660
+ );
661
+ return yield* Effect8.forEach(filePaths, (filePath) => {
662
+ return FileItem.new({
663
+ relativePath: filePath,
664
+ cwd: context.cwd
665
+ });
666
+ });
667
+ })
668
+ };
669
+ }),
670
+ dependencies: []
671
+ }) {
672
+ };
673
+
674
+ // src/errors/interop/file_object_wrapper.ts
675
+ import { Data as Data7 } from "effect";
676
+ var FileObjectWrapperError = class extends Data7.TaggedError("FileObjectWrapperError") {
677
+ };
678
+
679
+ // src/file_object/api_instance.ts
680
+ import { S3Client } from "@aws-sdk/client-s3";
681
+ import { Context as Context6, Effect as Effect9, Layer as Layer2 } from "effect";
682
+ var FileObjectApiInstance = class extends Context6.Tag("FileObjectApiInstance")() {
683
+ };
684
+ var FileObjectApiInstanceLive = Layer2.effect(
685
+ FileObjectApiInstance,
686
+ Effect9.gen(function* () {
687
+ const config = yield* InternalConfigContext;
688
+ return new S3Client({
689
+ region: config.bucket.params.region,
690
+ apiVersion: config.bucket.params.apiVersion,
691
+ endpoint: config.bucket.params.endpoint,
692
+ forcePathStyle: config.bucket.params.forcePathStyle,
693
+ credentials: {
694
+ accessKeyId: config.bucket.params.credentials.accessKeyId,
695
+ secretAccessKey: config.bucket.params.credentials.secretAccessKey
696
+ }
697
+ });
698
+ })
699
+ );
700
+
701
+ // src/file_object/repository.ts
702
+ import {
703
+ CreateBucketCommand,
704
+ HeadBucketCommand,
705
+ PutBucketAclCommand,
706
+ PutBucketWebsiteCommand,
707
+ PutObjectCommand
708
+ } from "@aws-sdk/client-s3";
709
+ import { Effect as Effect10 } from "effect";
710
+ var FileObjectRepository = class extends Effect10.Service()(
711
+ "FileObjectRepository",
712
+ {
713
+ effect: Effect10.gen(function* () {
714
+ const apiInstance = yield* FileObjectApiInstance;
715
+ const context = yield* FileObjectBucketContext;
716
+ return {
717
+ createBucket: genFn(function* () {
718
+ const command3 = new CreateBucketCommand({
719
+ Bucket: context.bucketName,
720
+ CreateBucketConfiguration: {
721
+ LocationConstraint: context.region
722
+ },
723
+ ObjectOwnership: "BucketOwnerEnforced"
724
+ });
725
+ yield* toEffect(apiInstance.send(command3), FileObjectWrapperError, {});
726
+ }),
727
+ doesBucketExist: genFn(function* () {
728
+ const command3 = new HeadBucketCommand({
729
+ Bucket: context.bucketName
730
+ });
731
+ const doesExist = yield* Effect10.matchEffect(
732
+ toEffect(apiInstance.send(command3), FileObjectWrapperError, {}),
733
+ {
734
+ onFailure: (error) => {
735
+ if (error.cause.name === "NotFound") {
736
+ return Effect10.succeed(false);
737
+ }
738
+ return Effect10.fail(error);
739
+ },
740
+ onSuccess: () => Effect10.succeed(true)
741
+ }
742
+ );
743
+ return doesExist;
744
+ }),
745
+ setPublicReadAclOnBucket: genFn(function* () {
746
+ const command3 = new PutBucketAclCommand({
747
+ Bucket: context.bucketName,
748
+ ACL: "public-read"
749
+ });
750
+ yield* toEffect(apiInstance.send(command3), FileObjectWrapperError, {});
751
+ }),
752
+ setWebsiteConfigurationOnBucket: genFn(function* (indexFileKeySuffix, errorFileKey) {
753
+ const command3 = new PutBucketWebsiteCommand({
754
+ Bucket: context.bucketName,
755
+ WebsiteConfiguration: {
756
+ IndexDocument: {
757
+ Suffix: indexFileKeySuffix
758
+ },
759
+ ErrorDocument: {
760
+ Key: errorFileKey
761
+ }
762
+ }
763
+ });
764
+ yield* toEffect(apiInstance.send(command3), FileObjectWrapperError, {});
765
+ }),
766
+ putObject: genFn(function* (file) {
767
+ const command3 = new PutObjectCommand({
768
+ ACL: "public-read",
769
+ Bucket: context.bucketName,
770
+ Key: file.key,
771
+ Body: file.content,
772
+ ContentEncoding: "binary",
773
+ ContentType: file.contentType,
774
+ CacheControl: file.cacheControlValue
775
+ });
776
+ yield* toEffect(apiInstance.send(command3), FileObjectWrapperError, {});
777
+ })
778
+ };
779
+ }),
780
+ dependencies: [FileObjectApiInstanceLive]
781
+ }
782
+ ) {
783
+ };
784
+
785
+ // src/helpers/file.ts
786
+ import { Path as Path3 } from "@effect/platform";
787
+ import { Array as Array2, Effect as Effect11, Option } from "effect";
788
+ var fileIntoObject = function(file) {
789
+ return Effect11.gen(function* () {
790
+ const content = yield* file.content;
791
+ const contentType = yield* file.contentType;
792
+ return {
793
+ key: file.relativePath,
794
+ content,
795
+ contentType,
796
+ cacheControlValue: void 0
797
+ };
798
+ });
799
+ };
800
+ var detectAndFillCacheControl = function(object, cacheControlMapping) {
801
+ for (const [keyRawRegExp, cacheControlValue] of Object.entries(cacheControlMapping)) {
802
+ const isCorrectCacheControl = new RegExp(keyRawRegExp).test(object.key);
803
+ if (isCorrectCacheControl) {
804
+ return {
805
+ ...object,
806
+ cacheControlValue
807
+ };
808
+ }
809
+ }
810
+ return object;
811
+ };
812
+ var extractFirstFolderFromPath = function(relativePath) {
813
+ return Effect11.gen(function* () {
814
+ const path = yield* Path3.Path;
815
+ const segments = path.normalize(relativePath).replace(/\\/g, "/").split("/").filter((segment) => segment.length > 0 && segment !== ".");
816
+ if (segments.length <= 1) {
817
+ return Option.none();
818
+ }
819
+ return Array2.head(segments);
820
+ });
821
+ };
822
+ var extractRootFileTree = genFn(function* (file, cwd) {
823
+ const firstFolder = yield* extractFirstFolderFromPath(file.relativePath);
824
+ if (Option.isNone(firstFolder)) {
825
+ return Option.none();
826
+ }
827
+ return Option.some(
828
+ yield* FileTree.new({ relativePath: firstFolder.value, cwd, items: [file] })
829
+ );
830
+ });
831
+ var hasMinOneFile = function(components) {
832
+ for (const component of components) {
833
+ if (component.length > 0) {
834
+ return true;
835
+ }
836
+ }
837
+ return false;
838
+ };
839
+ var excludeRootFile = function(components, filenameToExclude) {
840
+ return Effect11.sync(function* () {
841
+ for (const component of components) {
842
+ if (!FileTree.is(component) && component.name === filenameToExclude) {
843
+ continue;
844
+ }
845
+ yield component;
846
+ }
847
+ });
848
+ };
849
+ var extractRootFile = genFn(function* (components, filenameToPick) {
850
+ for (const component of components) {
851
+ if (!FileTree.is(component) && component.name === filenameToPick) {
852
+ return component;
853
+ }
854
+ }
855
+ return yield* Effect11.fail(
856
+ new FileNotFoundError({ message: `File not found`, file: { name: filenameToPick } })
857
+ );
858
+ });
859
+ var pickIndexHtml = genFn(function* (components) {
860
+ const alteredComponents = yield* excludeRootFile(components, "index.html");
861
+ const indexHtml = yield* extractRootFile(components, "index.html");
862
+ return {
863
+ components: alteredComponents,
864
+ indexHtml
865
+ };
866
+ });
867
+
868
+ // src/front/service.ts
869
+ import { Effect as Effect12, HashMap, Layer as Layer3, Option as Option2 } from "effect";
870
+ var FileRepositoryContextLive = Layer3.effect(
871
+ FileRepositoryContext,
872
+ Effect12.gen(function* () {
873
+ const front = yield* FrontDeploymentContext;
874
+ return { cwd: front.buildOutputPath };
875
+ })
876
+ );
877
+ var FileObjectBucketContextLive = Layer3.effect(
878
+ FileObjectBucketContext,
879
+ Effect12.gen(function* () {
880
+ const front = yield* FrontDeploymentContext;
881
+ const config = yield* InternalConfigContext;
882
+ return {
883
+ bucketName: front.bucketName,
884
+ region: config.bucket.params.region
885
+ };
886
+ })
887
+ );
888
+ var FrontFileObjectService = class extends Effect12.Service()(
889
+ "FrontFileObjectService",
890
+ {
891
+ effect: Effect12.gen(function* () {
892
+ const configContext = yield* InternalConfigContext;
893
+ const fileRepository = yield* FileRepository;
894
+ const fileObjectRepository = yield* FileObjectRepository;
895
+ const frontContext = yield* FrontDeploymentContext;
896
+ return {
897
+ doesFrontBucketExist: () => {
898
+ return fileObjectRepository.doesBucketExist();
899
+ },
900
+ listFrontBuildFiles: () => {
901
+ return fileRepository.listFiles();
902
+ },
903
+ listFrontBuildFileByRootComponents: genFn(function* () {
904
+ const files = yield* fileRepository.listFiles();
905
+ let map = HashMap.empty();
906
+ for (const file of files) {
907
+ const resolvedFileTree = yield* extractRootFileTree(
908
+ file,
909
+ frontContext.buildOutputPath
910
+ );
911
+ if (Option2.isNone(resolvedFileTree)) {
912
+ map = HashMap.set(map, file.absolutePath, file);
913
+ continue;
914
+ }
915
+ const existingFileTree = HashMap.get(map, resolvedFileTree.value.absolutePath);
916
+ if (Option2.isNone(existingFileTree)) {
917
+ map = HashMap.set(
918
+ map,
919
+ resolvedFileTree.value.absolutePath,
920
+ resolvedFileTree.value
921
+ );
922
+ continue;
923
+ }
924
+ if (!FileTree.is(existingFileTree.value)) {
925
+ return yield* Effect12.fail(
926
+ new FileError({
927
+ message: `The same path was resolved both file item & file tree`,
928
+ file: existingFileTree.value
929
+ })
930
+ );
931
+ }
932
+ const updatedFileTree = yield* existingFileTree.value.append([file]);
933
+ map = HashMap.set(map, existingFileTree.value.absolutePath, updatedFileTree);
934
+ }
935
+ return HashMap.values(map);
936
+ }),
937
+ createFrontBucket: genFn(function* () {
938
+ yield* fileObjectRepository.createBucket();
939
+ yield* fileObjectRepository.setPublicReadAclOnBucket();
940
+ yield* fileObjectRepository.setWebsiteConfigurationOnBucket(
941
+ configContext.bucket.front.indexDocumentSuffix,
942
+ configContext.bucket.front.errorDocumentKey
943
+ );
944
+ }),
945
+ uploadFrontFileToBucket: genFn(function* (file) {
946
+ const object = detectAndFillCacheControl(
947
+ yield* fileIntoObject(file),
948
+ configContext.bucket.front.cacheControlMapping
949
+ );
950
+ yield* fileObjectRepository.putObject(object);
951
+ })
952
+ };
953
+ }),
954
+ dependencies: [
955
+ FileRepository.Default.pipe(Layer3.provide(FileRepositoryContextLive)),
956
+ FileObjectRepository.Default.pipe(Layer3.provide(FileObjectBucketContextLive))
957
+ ]
958
+ }
959
+ ) {
960
+ };
961
+
962
+ // src/ui/confirm.ts
963
+ import { confirm } from "@clack/prompts";
964
+ var genConfirmUi = function({
965
+ question,
966
+ initialValue
967
+ }) {
968
+ return toEffect(
969
+ confirm({
970
+ message: question,
971
+ initialValue
972
+ }),
973
+ TUiWrapperError,
974
+ {
975
+ uiFunction: "confirm"
976
+ }
977
+ );
978
+ };
979
+
980
+ // src/ui/loader.ts
981
+ import { spinner } from "@clack/prompts";
982
+ import { Effect as Effect13 } from "effect";
983
+ var genLoaderUi = function({
984
+ process: process2,
985
+ message
986
+ }) {
987
+ return Effect13.gen(function* () {
988
+ const spin = spinner({
989
+ indicator: "timer",
990
+ frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
991
+ delay: 80,
992
+ styleFrame: (frame) => `\x1B[35m${frame}\x1B[0m`
993
+ });
994
+ return yield* interceptProcessExit(
995
+ Effect13.gen(function* () {
996
+ spin.start(message.resolveStart());
997
+ const [duration, result] = yield* Effect13.timed(
998
+ process2(spin.message).pipe(
999
+ Effect13.catchAll(
1000
+ (error) => Effect13.gen(function* () {
1001
+ spin.error(message.resolveError(error));
1002
+ return yield* Effect13.fail(error);
1003
+ })
1004
+ )
1005
+ )
1006
+ );
1007
+ spin.stop(message.resolveEnd(duration));
1008
+ return result;
1009
+ }),
1010
+ (exitCode) => spin.cancel(message.resolveCancel(exitCode))
1011
+ );
1012
+ });
1013
+ };
1014
+
1015
+ // src/use_cases/create_front_bucket.ts
1016
+ import { log as log3 } from "@clack/prompts";
1017
+ import { Duration as Duration2, Effect as Effect14 } from "effect";
1018
+ var CreateFrontBucketUseCase = class extends Effect14.Service()(
1019
+ "CreateFrontBucketUseCase",
1020
+ {
1021
+ effect: Effect14.gen(function* () {
1022
+ const deploymentContext = yield* FrontDeploymentContext;
1023
+ const frontService = yield* FrontFileObjectService;
1024
+ return {
1025
+ run: genFn(function* () {
1026
+ const doesBucketExist = yield* frontService.doesFrontBucketExist();
1027
+ if (doesBucketExist) {
1028
+ log3.info(`The bucket '${deploymentContext.bucketName}' already exist`);
1029
+ return yield* Effect14.interrupt;
1030
+ }
1031
+ const shouldContinue = yield* genConfirmUi({
1032
+ question: `Do you want to create the bucket '${deploymentContext.bucketName}'?`,
1033
+ initialValue: true
1034
+ });
1035
+ if (!shouldContinue) {
1036
+ return yield* Effect14.interrupt;
1037
+ }
1038
+ yield* genLoaderUi({
1039
+ process: frontService.createFrontBucket,
1040
+ message: {
1041
+ resolveStart: () => "Creating front bucket",
1042
+ resolveError: (error) => `Creation failed: ${error.message}`,
1043
+ resolveCancel: () => "Creation canceled",
1044
+ resolveEnd: (duration) => `Creation finished in ${Duration2.toMillis(duration)}ms`
1045
+ }
1046
+ });
1047
+ })
1048
+ };
1049
+ }),
1050
+ dependencies: [FrontFileObjectService.Default]
1051
+ }
1052
+ ) {
1053
+ };
1054
+
1055
+ // src/cli/create_front_bucket_command.ts
1056
+ import { cancel, log as log4, outro } from "@clack/prompts";
1057
+ import { command } from "cmd-ts";
1058
+ import { Effect as Effect15, Inspectable, Layer as Layer4 } from "effect";
1059
+ var createFrontBucketCommand = command({
1060
+ name: "create",
1061
+ description: "TODO",
1062
+ args: {},
1063
+ handler: function() {
1064
+ const CommandContextLayer = Layer4.succeed(CliCommandContext, {
1065
+ commandName: this.name
1066
+ });
1067
+ return Effect15.gen(function* () {
1068
+ const { config } = yield* startCli();
1069
+ const ConfigContextLayer = Layer4.succeed(InternalConfigContext, config);
1070
+ const DeploymentContextLayer = makeFrontDeploymentContextLayer(config);
1071
+ yield* Effect15.gen(function* () {
1072
+ const useCase = yield* CreateFrontBucketUseCase;
1073
+ yield* runAndInterruptOnCtrlC(useCase.run());
1074
+ outro(`Creation finished`);
1075
+ }).pipe(
1076
+ Effect15.provide(CreateFrontBucketUseCase.Default),
1077
+ Effect15.provide(ConfigContextLayer),
1078
+ Effect15.provide(DeploymentContextLayer),
1079
+ Effect15.onInterrupt(() => Effect15.sync(() => cancel(`Creation canceled`))),
1080
+ Effect15.catchAll(
1081
+ (error) => Effect15.gen(function* () {
1082
+ log4.error(error.message);
1083
+ log4.message(Inspectable.toStringUnknown(error));
1084
+ outro(`Creation aborted`);
1085
+ })
1086
+ )
1087
+ );
1088
+ }).pipe(Effect15.provide(CommandContextLayer));
1089
+ }
1090
+ });
1091
+
1092
+ // src/errors/front.ts
1093
+ import { Data as Data8 } from "effect";
1094
+ var FrontError = class extends Data8.TaggedError("FrontError") {
1095
+ };
1096
+
1097
+ // src/errors/command.ts
1098
+ import { Data as Data9 } from "effect";
1099
+ var CommandError = class extends Data9.TaggedError("CommandError") {
1100
+ };
1101
+
1102
+ // src/helpers/command.ts
1103
+ import { Command as Command2 } from "@effect/platform";
1104
+ import { Effect as Effect16, pipe, Stream } from "effect";
1105
+ var runCommand = function(command3, logMessage) {
1106
+ return Effect16.scoped(
1107
+ pipe(
1108
+ Command2.start(command3.pipe(Command2.runInShell(true))),
1109
+ Effect16.flatMap(
1110
+ (process2) => Effect16.all(
1111
+ [
1112
+ process2.exitCode,
1113
+ process2.stdout.pipe(
1114
+ Stream.decodeText(),
1115
+ Stream.runForEach((chunk) => Effect16.sync(() => logMessage(chunk)))
1116
+ ),
1117
+ process2.stderr.pipe(
1118
+ Stream.decodeText(),
1119
+ Stream.runForEach((chunk) => Effect16.sync(() => logMessage(chunk)))
1120
+ )
1121
+ ],
1122
+ { concurrency: 3 }
1123
+ )
1124
+ ),
1125
+ Effect16.map((result) => result[0])
1126
+ )
1127
+ );
1128
+ };
1129
+
1130
+ // src/ui/command.ts
1131
+ import { Effect as Effect17 } from "effect";
1132
+ var genCommandUi = function({
1133
+ command: command3,
1134
+ message
1135
+ }) {
1136
+ return genLoaderUi({
1137
+ process: (logMessage) => Effect17.gen(function* () {
1138
+ const exitCode = yield* runCommand(command3, logMessage);
1139
+ if (exitCode !== 0) {
1140
+ return yield* Effect17.fail(
1141
+ new CommandError({
1142
+ message: `Command exited with code '${exitCode}'`,
1143
+ code: exitCode
1144
+ })
1145
+ );
1146
+ }
1147
+ return exitCode;
1148
+ }),
1149
+ message
1150
+ });
1151
+ };
1152
+
1153
+ // src/ui/tasks.ts
1154
+ import { taskLog } from "@clack/prompts";
1155
+ import { Effect as Effect18 } from "effect";
1156
+ var genTaskLogsUi = ({
1157
+ title,
1158
+ itemGroups,
1159
+ processItem,
1160
+ message,
1161
+ subTaskConcurrency
1162
+ }) => {
1163
+ return Effect18.gen(function* () {
1164
+ const log7 = taskLog({
1165
+ title,
1166
+ retainLog: false
1167
+ });
1168
+ const tasks = Array.from(itemGroups).map((group) => {
1169
+ return Effect18.gen(function* () {
1170
+ const groupLog = log7.group(message.resolveGroupTitle(group));
1171
+ const [duration2] = yield* Effect18.timed(
1172
+ Effect18.gen(function* () {
1173
+ const itemProcesses = Array.from(group).map(
1174
+ (item) => Effect18.gen(function* () {
1175
+ yield* processItem(item);
1176
+ groupLog.message(message.resolveItem(item));
1177
+ })
1178
+ );
1179
+ yield* Effect18.all(itemProcesses, {
1180
+ concurrency: subTaskConcurrency
1181
+ });
1182
+ }).pipe(
1183
+ Effect18.catchAll(
1184
+ (error) => Effect18.gen(function* () {
1185
+ groupLog.error(message.resolveGroupError(group, error));
1186
+ return yield* Effect18.fail(error);
1187
+ })
1188
+ )
1189
+ )
1190
+ );
1191
+ groupLog.success(message.resolveGroupSuccess(group, duration2));
1192
+ });
1193
+ });
1194
+ const [duration] = yield* Effect18.timed(
1195
+ Effect18.all(tasks, {
1196
+ concurrency: "unbounded"
1197
+ })
1198
+ );
1199
+ log7.success(message.resolveSuccess(duration));
1200
+ });
1201
+ };
1202
+
1203
+ // src/use_cases/deploy_on_bucket.ts
1204
+ import { log as log5 } from "@clack/prompts";
1205
+ import { Duration as Duration5, Effect as Effect19 } from "effect";
1206
+ var DeployOnBucketUseCase = class extends Effect19.Service()(
1207
+ "DeployOnBucketUseCase",
1208
+ {
1209
+ effect: Effect19.gen(function* () {
1210
+ const frontService = yield* FrontFileObjectService;
1211
+ const deploymentContext = yield* FrontDeploymentContext;
1212
+ return {
1213
+ run: genFn(function* () {
1214
+ const shouldContinue = yield* genConfirmUi({
1215
+ question: `Do you want to build and upload '${deploymentContext.buildOutputPath}' to bucket '${deploymentContext.bucketName}'?`,
1216
+ initialValue: false
1217
+ });
1218
+ if (!shouldContinue) {
1219
+ return yield* Effect19.interrupt;
1220
+ }
1221
+ const doesBucketExist = yield* frontService.doesFrontBucketExist();
1222
+ if (!doesBucketExist) {
1223
+ log5.error(`Bucket '${deploymentContext.bucketName}' should exist`);
1224
+ return yield* Effect19.interrupt;
1225
+ }
1226
+ yield* genCommandUi({
1227
+ command: deploymentContext.command,
1228
+ message: {
1229
+ resolveStart: () => "Building front",
1230
+ resolveError: (error) => `Build failed: ${error.message}`,
1231
+ resolveCancel: () => "Build canceled",
1232
+ resolveEnd: (duration) => `Build finished in ${Duration5.toMillis(duration)}ms`
1233
+ }
1234
+ });
1235
+ const configContext = yield* InternalConfigContext;
1236
+ const frontBuildFileComponents = yield* frontService.listFrontBuildFileByRootComponents();
1237
+ const hasOneFile = hasMinOneFile(frontBuildFileComponents);
1238
+ if (!hasOneFile) {
1239
+ log5.error("Front files should contain minimum one file");
1240
+ return yield* Effect19.interrupt;
1241
+ }
1242
+ const { components: frontBuildFileComponentRests, indexHtml } = yield* pickIndexHtml(frontBuildFileComponents).pipe(
1243
+ Effect19.catchTag(
1244
+ "FileNotFoundError",
1245
+ () => new FrontError({
1246
+ message: "'index.html' file should exist in the front build folder",
1247
+ config: configContext.front
1248
+ })
1249
+ )
1250
+ );
1251
+ yield* genTaskLogsUi({
1252
+ title: "Uploading common files",
1253
+ itemGroups: Array.from(frontBuildFileComponentRests),
1254
+ processItem: frontService.uploadFrontFileToBucket,
1255
+ message: {
1256
+ resolveGroupTitle: (fileComponent) => `Uploading ${fileComponent.name}`,
1257
+ resolveGroupSuccess: (fileComponent, duration) => `${fileComponent.name}: ${fileComponent.length} files uploaded in ${Duration5.toMillis(duration)}ms`,
1258
+ resolveGroupError: (fileComponent, error) => `Upload ${fileComponent.name} failed: ${error.message}`,
1259
+ resolveItem: (fileItem) => `File ${fileItem.relativePath} uploaded`,
1260
+ resolveSuccess: (duration) => `Common files uploaded in ${Duration5.toMillis(duration)}ms`
1261
+ },
1262
+ subTaskConcurrency: configContext.bucket.upload.concurrency
1263
+ });
1264
+ yield* genLoaderUi({
1265
+ process: () => frontService.uploadFrontFileToBucket(indexHtml),
1266
+ message: {
1267
+ resolveStart: () => "Uploading index.html",
1268
+ resolveError: (error) => `Upload index.html failed: ${error.message}`,
1269
+ resolveCancel: () => "Upload index.html canceled",
1270
+ resolveEnd: (duration) => `Upload index.html finished in ${Duration5.toMillis(duration)}ms`
1271
+ }
1272
+ });
1273
+ })
1274
+ };
1275
+ }),
1276
+ dependencies: [FrontFileObjectService.Default]
1277
+ }
1278
+ ) {
1279
+ };
1280
+
1281
+ // src/cli/deploy_on_bucket_command.ts
1282
+ import { cancel as cancel2, log as log6, outro as outro2 } from "@clack/prompts";
1283
+ import { command as command2 } from "cmd-ts";
1284
+ import { Effect as Effect20, Inspectable as Inspectable2, Layer as Layer5 } from "effect";
1285
+ var deployOnBucketCommand = command2({
1286
+ name: "deploy",
1287
+ description: "TODO",
1288
+ args: {},
1289
+ handler: function() {
1290
+ const CommandContextLayer = Layer5.succeed(CliCommandContext, {
1291
+ commandName: this.name
1292
+ });
1293
+ return Effect20.gen(function* () {
1294
+ const { config } = yield* startCli();
1295
+ const ConfigContextLayer = Layer5.succeed(InternalConfigContext, config);
1296
+ const DeploymentContextLayer = makeFrontDeploymentContextLayer(config);
1297
+ yield* Effect20.gen(function* () {
1298
+ const useCase = yield* DeployOnBucketUseCase;
1299
+ yield* runAndInterruptOnCtrlC(useCase.run());
1300
+ outro2(`Deployment finished`);
1301
+ }).pipe(
1302
+ Effect20.provide(DeployOnBucketUseCase.Default),
1303
+ Effect20.provide(ConfigContextLayer),
1304
+ Effect20.provide(DeploymentContextLayer),
1305
+ Effect20.onInterrupt(() => Effect20.sync(() => cancel2(`Deployment canceled`))),
1306
+ Effect20.catchAll(
1307
+ (error) => Effect20.gen(function* () {
1308
+ log6.error(error.message);
1309
+ log6.message(Inspectable2.toStringUnknown(error));
1310
+ outro2(`Deployment aborted`);
1311
+ })
1312
+ )
1313
+ );
1314
+ }).pipe(Effect20.provide(CommandContextLayer));
1315
+ }
1316
+ });
1317
+
1318
+ // src/cli/cli.ts
1319
+ import { subcommands } from "cmd-ts";
1320
+ var cli = subcommands({
1321
+ name: package_default.name,
1322
+ cmds: { deploy: deployOnBucketCommand, create: createFrontBucketCommand }
1323
+ });
1324
+
1325
+ // src/errors/internal.ts
1326
+ import { Data as Data10 } from "effect";
1327
+ var InternalError = class extends Data10.TaggedError("InternalError") {
1328
+ };
1329
+
1330
+ // main.ts
1331
+ import { NodeContext, NodeRuntime } from "@effect/platform-node";
1332
+ import { run } from "cmd-ts";
1333
+ import { Effect as Effect21 } from "effect";
1334
+ var runCli = toEffect(run(cli, process.argv.slice(2)), InternalError, {}).pipe(
1335
+ Effect21.flatMap(({ value }) => value)
1336
+ );
1337
+ NodeRuntime.runMain(runCli.pipe(Effect21.provide(NodeContext.layer)));