@shivaedev/test-story 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +21 -0
  3. package/README.md +284 -1
  4. package/dist/genreTags.d.ts +6 -0
  5. package/dist/genreTags.d.ts.map +1 -0
  6. package/dist/genreTags.js +5 -0
  7. package/dist/genreTags.js.map +1 -0
  8. package/dist/internal/callSite.d.ts +3 -0
  9. package/dist/internal/callSite.d.ts.map +1 -0
  10. package/dist/internal/callSite.js +17 -0
  11. package/dist/internal/callSite.js.map +1 -0
  12. package/dist/internal/engineSnapshot.d.ts +4 -0
  13. package/dist/internal/engineSnapshot.d.ts.map +1 -0
  14. package/dist/internal/engineSnapshot.js +36 -0
  15. package/dist/internal/engineSnapshot.js.map +1 -0
  16. package/dist/internal/errors.d.ts +5 -0
  17. package/dist/internal/errors.d.ts.map +1 -0
  18. package/dist/internal/errors.js +15 -0
  19. package/dist/internal/errors.js.map +1 -0
  20. package/dist/internal/failureReport.d.ts +8 -0
  21. package/dist/internal/failureReport.d.ts.map +1 -0
  22. package/dist/internal/failureReport.js +41 -0
  23. package/dist/internal/failureReport.js.map +1 -0
  24. package/dist/internal/genre.d.ts +7 -0
  25. package/dist/internal/genre.d.ts.map +1 -0
  26. package/dist/internal/genre.js +8 -0
  27. package/dist/internal/genre.js.map +1 -0
  28. package/dist/internal/hooked.d.ts +7 -0
  29. package/dist/internal/hooked.d.ts.map +1 -0
  30. package/dist/internal/hooked.js +11 -0
  31. package/dist/internal/hooked.js.map +1 -0
  32. package/dist/internal/narration.d.ts +25 -0
  33. package/dist/internal/narration.d.ts.map +1 -0
  34. package/dist/internal/narration.js +34 -0
  35. package/dist/internal/narration.js.map +1 -0
  36. package/dist/internal/render.d.ts +8 -0
  37. package/dist/internal/render.d.ts.map +1 -0
  38. package/dist/internal/render.js +56 -0
  39. package/dist/internal/render.js.map +1 -0
  40. package/dist/internal/runUntil.d.ts +17 -0
  41. package/dist/internal/runUntil.d.ts.map +1 -0
  42. package/dist/internal/runUntil.js +24 -0
  43. package/dist/internal/runUntil.js.map +1 -0
  44. package/dist/internal/seed.d.ts +29 -0
  45. package/dist/internal/seed.d.ts.map +1 -0
  46. package/dist/internal/seed.js +27 -0
  47. package/dist/internal/seed.js.map +1 -0
  48. package/dist/internal/start.d.ts +7 -0
  49. package/dist/internal/start.d.ts.map +1 -0
  50. package/dist/internal/start.js +23 -0
  51. package/dist/internal/start.js.map +1 -0
  52. package/dist/internal/storyIt.d.ts +16 -0
  53. package/dist/internal/storyIt.d.ts.map +1 -0
  54. package/dist/internal/storyIt.js +51 -0
  55. package/dist/internal/storyIt.js.map +1 -0
  56. package/dist/internal/traits.d.ts +12 -0
  57. package/dist/internal/traits.d.ts.map +1 -0
  58. package/dist/internal/traits.js +8 -0
  59. package/dist/internal/traits.js.map +1 -0
  60. package/dist/internal/wrap.d.ts +8 -0
  61. package/dist/internal/wrap.d.ts.map +1 -0
  62. package/dist/internal/wrap.js +49 -0
  63. package/dist/internal/wrap.js.map +1 -0
  64. package/dist/storyKit.d.ts +67 -0
  65. package/dist/storyKit.d.ts.map +1 -0
  66. package/dist/storyKit.js +11 -0
  67. package/dist/storyKit.js.map +1 -0
  68. package/docs/north-star.md +35 -0
  69. package/docs/roadmap.md +23 -0
  70. package/package.json +67 -3
  71. package/src/genreTags.ts +10 -0
  72. package/src/internal/callSite.ts +19 -0
  73. package/src/internal/engineSnapshot.ts +48 -0
  74. package/src/internal/errors.ts +39 -0
  75. package/src/internal/failureReport.ts +55 -0
  76. package/src/internal/genre.ts +14 -0
  77. package/src/internal/hooked.ts +22 -0
  78. package/src/internal/narration.ts +57 -0
  79. package/src/internal/render.ts +69 -0
  80. package/src/internal/runUntil.ts +43 -0
  81. package/src/internal/seed.ts +56 -0
  82. package/src/internal/start.ts +24 -0
  83. package/src/internal/storyIt.ts +68 -0
  84. package/src/internal/traits.ts +20 -0
  85. package/src/internal/wrap.ts +50 -0
  86. package/src/storyKit.ts +130 -0
  87. package/login.log +0 -26
  88. package/publish.log +0 -0
  89. package/run.out +0 -26
  90. package/run.sh +0 -10
@@ -0,0 +1,68 @@
1
+ import type { TestContext } from "@effect/vitest";
2
+ import { Effect, Layer } from "effect";
3
+ import type { EffectTest, EffectTestOptions } from "@shivaedev/effect-test/types.ts";
4
+ import { makeEffectIt } from "@shivaedev/effect-test/vitest.ts";
5
+ import { type Genre, genreOf } from "#internal/genre.ts";
6
+ import { type AnyEffect, hooked } from "#internal/hooked.ts";
7
+ import type { LooseDefinition, LooseTrait } from "#internal/seed.ts";
8
+ import { start } from "#internal/start.ts";
9
+
10
+ type LooseBody = (told: object, context: TestContext) => Generator<AnyEffect, unknown, never> | undefined;
11
+
12
+ type LooseTest = (name: string, given: readonly LooseTrait[], body?: LooseBody, options?: number | EffectTestOptions) => void;
13
+
14
+ export interface LooseIt extends LooseTest {
15
+ readonly fails: LooseTest;
16
+ readonly only: LooseTest;
17
+ readonly runIf: (condition: unknown) => LooseTest;
18
+ readonly skip: LooseTest;
19
+ readonly skipIf: (condition: unknown) => LooseTest;
20
+ }
21
+
22
+ function withTags(options: number | EffectTestOptions | undefined, tags: readonly string[]): EffectTestOptions {
23
+ if (typeof options === "number") {
24
+ return { tags: [...tags], timeout: options };
25
+ }
26
+ const own = options?.tags ?? [];
27
+ return { ...options, tags: [...tags, ...(typeof own === "string" ? [own] : own)] };
28
+ }
29
+
30
+ function storyOf(definition: LooseDefinition, given: readonly LooseTrait[], body: LooseBody | undefined) {
31
+ return function* story(_harness: unknown, context: TestContext) {
32
+ const told = yield* start(definition, given, context);
33
+ return yield* hooked(() => body?.(told, context));
34
+ };
35
+ }
36
+
37
+ // Vitest refuses a tag its config does not declare, so a genre the config leaves out names the test without tagging it.
38
+ function declaring(definition: LooseDefinition, genre: Genre, declare: EffectTest<unknown, unknown>): LooseTest {
39
+ return (name, given, body, options) => {
40
+ const title = `${genre.title}: ${name}`;
41
+ const story = storyOf(definition, given, body);
42
+ try {
43
+ declare(title, story, withTags(options, [genre.tag.name]));
44
+ } catch (error) {
45
+ if (!(error instanceof Error && error.message.includes(`"${genre.tag.name}"`))) {
46
+ throw error;
47
+ }
48
+ declare(title, story, withTags(options, []));
49
+ }
50
+ };
51
+ }
52
+
53
+ export function storyIt(definition: LooseDefinition): LooseIt {
54
+ const genre = genreOf(definition.name);
55
+ const { effectApp } = makeEffectIt({
56
+ around: definition.around,
57
+ clock: definition.clock,
58
+ layer: definition.layer ?? Layer.empty,
59
+ makeHarness: () => Effect.void,
60
+ });
61
+ return Object.assign(declaring(definition, genre, effectApp), {
62
+ fails: declaring(definition, genre, effectApp.fails),
63
+ only: declaring(definition, genre, effectApp.only),
64
+ runIf: (condition: unknown) => declaring(definition, genre, effectApp.runIf(condition)),
65
+ skip: declaring(definition, genre, effectApp.skip),
66
+ skipIf: (condition: unknown) => declaring(definition, genre, effectApp.skipIf(condition)),
67
+ });
68
+ }
@@ -0,0 +1,20 @@
1
+ import { callSite } from "#internal/callSite.ts";
2
+
3
+ export interface TraitPart<TStage extends string, TApply> {
4
+ readonly apply: TApply;
5
+ readonly line: string;
6
+ readonly site: Error;
7
+ readonly stage: TStage;
8
+ }
9
+
10
+ export interface Parts<TStage extends string, TApply> {
11
+ readonly parts: readonly TraitPart<TStage, TApply>[];
12
+ }
13
+
14
+ export function trait<TStage extends string, TApply>(stage: TStage, line: string, apply: TApply): Parts<TStage, TApply> {
15
+ return { parts: [{ apply, line, site: callSite(), stage }] };
16
+ }
17
+
18
+ export function traits<TStage extends string, TApply>(...given: readonly Parts<TStage, TApply>[]): Parts<TStage, TApply> {
19
+ return { parts: given.flatMap((each) => each.parts) };
20
+ }
@@ -0,0 +1,50 @@
1
+ export const WIDTH = 120;
2
+
3
+ export function wrap(text: string, { first = "", rest = "" }: { readonly first?: string; readonly rest?: string } = {}): string[] {
4
+ const lines: string[] = [];
5
+ let line = first;
6
+ for (const word of text.split(" ")) {
7
+ const prefix = lines.length === 0 ? first : rest;
8
+ if (line.length > prefix.length && line.length + 1 + word.length > WIDTH) {
9
+ lines.push(line);
10
+ line = `${rest}${word}`;
11
+ } else {
12
+ line = line.length > prefix.length ? `${line} ${word}` : `${line}${word}`;
13
+ }
14
+ }
15
+ return [...lines, line];
16
+ }
17
+
18
+ export function explained(what: string, help: string): string {
19
+ return [...wrap(what, { rest: " " }), ...wrap(help, { first: "help: ", rest: " " })].join("\n");
20
+ }
21
+
22
+ function members(json: string): string[] {
23
+ const value: unknown = JSON.parse(json);
24
+ if (Array.isArray(value)) {
25
+ return value.map((item) => JSON.stringify(item));
26
+ }
27
+ return typeof value === "object" && value !== null
28
+ ? Object.entries(value).map(([key, item]) => `${JSON.stringify(key)}:${JSON.stringify(item)}`)
29
+ : [json];
30
+ }
31
+
32
+ export function wrapJson(json: string): string[] {
33
+ const [open, close] = json.startsWith("[") ? ["[", "]"] : ["{", "}"];
34
+ const pieces = members(json);
35
+ if (pieces.length < 2) {
36
+ return [json];
37
+ }
38
+ const lines: string[] = [];
39
+ let line = open;
40
+ for (const [index, piece] of pieces.entries()) {
41
+ const next = `${piece}${index === pieces.length - 1 ? close : ","}`;
42
+ if (line.length > 1 && line.length + next.length > WIDTH) {
43
+ lines.push(line);
44
+ line = next;
45
+ } else {
46
+ line += next;
47
+ }
48
+ }
49
+ return [...lines, line];
50
+ }
@@ -0,0 +1,130 @@
1
+ import type { TestContext } from "@effect/vitest";
2
+ import type { Effect, Layer, Scope } from "effect";
3
+ import type { EffectClock, EffectTestOptions } from "@shivaedev/effect-test/types.ts";
4
+ import { type AnyEffect, type ErrorOf, type Hook, hooked, type ServicesOf } from "#internal/hooked.ts";
5
+ import type { LooseDefinition, LooseTrait } from "#internal/seed.ts";
6
+ import { type LooseIt, storyIt } from "#internal/storyIt.ts";
7
+ import { type Parts, trait, traits } from "#internal/traits.ts";
8
+
9
+ export type Trait<TEngine, TStage extends string, R = never> = Parts<TStage, (engine: TEngine) => Effect.Effect<void, unknown, R>>;
10
+
11
+ type TraitServices<TTrait> = TTrait extends Parts<string, (engine: never) => Effect.Effect<void, unknown, infer R>> ? R : never;
12
+
13
+ type Needs<TProvided> = Effect.Effect<unknown, unknown, TProvided>;
14
+
15
+ // A plain after hook yields nothing to infer, so TypeScript leaves its effect at the AnyEffect constraint.
16
+ type AfterEffects<TAfter> = { readonly [TKey in keyof TAfter]-?: AnyEffect extends TAfter[TKey] ? never : TAfter[TKey] }[keyof TAfter];
17
+
18
+ type LayerFor<THooks, TProvided> = [Exclude<ServicesOf<THooks>, Scope.Scope>] extends [TProvided]
19
+ ? unknown
20
+ : { readonly layer: Layer.Layer<Exclude<ServicesOf<THooks>, Scope.Scope>, unknown, never> };
21
+
22
+ export interface RunUntilOptions {
23
+ readonly maxSteps?: number | undefined;
24
+ }
25
+
26
+ export interface Story<TEngine, TRun extends AnyEffect = never> {
27
+ readonly engine: TEngine;
28
+ readonly lines: readonly string[];
29
+ readonly runUntil: <TUntil extends AnyEffect = never>(
30
+ until: Hook<[engine: TEngine], TUntil, boolean>,
31
+ options?: RunUntilOptions,
32
+ ) => Effect.Effect<void, ErrorOf<TRun | TUntil>, ServicesOf<TRun | TUntil>>;
33
+ readonly tell: (line: string) => void;
34
+ }
35
+
36
+ export interface RunHooks<TEngine, TStep extends AnyEffect, TFailed extends AnyEffect, TDiagnose extends AnyEffect> {
37
+ readonly diagnose?: Hook<[engine: TEngine], TDiagnose, string>;
38
+ readonly failed?: Hook<[engine: TEngine], TFailed, string | undefined>;
39
+ readonly maxSteps: number;
40
+ readonly step: Hook<[engine: TEngine, tell: (line: string) => void], TStep, void>;
41
+ }
42
+
43
+ export interface StoryKitDefinition<
44
+ TEngine,
45
+ TStage extends string,
46
+ TVerbs,
47
+ TProvided,
48
+ TCreate extends AnyEffect,
49
+ TAfter extends { readonly [TKey in TStage]?: AnyEffect },
50
+ TStep extends AnyEffect,
51
+ TFailed extends AnyEffect,
52
+ TDiagnose extends AnyEffect,
53
+ > {
54
+ readonly after?: { readonly [TKey in keyof TAfter]: Hook<[engine: TEngine], TAfter[TKey] & AnyEffect, void> };
55
+ readonly around?: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, unknown, R | NoInfer<TProvided> | Scope.Scope>;
56
+ readonly clock?: EffectClock;
57
+ readonly create: Hook<[], TCreate, TEngine>;
58
+ readonly inspect?: (engine: TEngine) => unknown;
59
+ readonly layer?: Layer.Layer<TProvided, unknown, never>;
60
+ readonly name: string;
61
+ readonly run?: RunHooks<TEngine, TStep, TFailed, TDiagnose>;
62
+ readonly stages: readonly [TStage, ...TStage[]];
63
+ readonly verbs: (engine: TEngine, story: Story<TEngine, TStep | TFailed | TDiagnose>) => TVerbs;
64
+ }
65
+
66
+ export type Told<TEngine, TVerbs, TRun extends AnyEffect> = TVerbs & { readonly story: Story<TEngine, TRun> };
67
+
68
+ export type StoryBody<TEngine, TVerbs, TRun extends AnyEffect, TEffect extends AnyEffect> = (
69
+ told: Told<TEngine, TVerbs, TRun>,
70
+ context: TestContext,
71
+ ) => Generator<TEffect, unknown, never> | undefined;
72
+
73
+ export type StoryTest<TEngine, TStage extends string, TVerbs, TRun extends AnyEffect, TProvided> = <
74
+ TEffect extends Needs<TProvided | Scope.Scope> = never,
75
+ >(
76
+ name: string,
77
+ given: readonly Trait<TEngine, TStage, TProvided | Scope.Scope>[],
78
+ body?: StoryBody<TEngine, TVerbs, TRun, TEffect>,
79
+ options?: number | EffectTestOptions,
80
+ ) => void;
81
+
82
+ export interface StoryIt<TEngine, TStage extends string, TVerbs, TRun extends AnyEffect, TProvided>
83
+ extends StoryTest<TEngine, TStage, TVerbs, TRun, TProvided> {
84
+ readonly fails: StoryTest<TEngine, TStage, TVerbs, TRun, TProvided>;
85
+ readonly only: StoryTest<TEngine, TStage, TVerbs, TRun, TProvided>;
86
+ readonly runIf: (condition: unknown) => StoryTest<TEngine, TStage, TVerbs, TRun, TProvided>;
87
+ readonly skip: StoryTest<TEngine, TStage, TVerbs, TRun, TProvided>;
88
+ readonly skipIf: (condition: unknown) => StoryTest<TEngine, TStage, TVerbs, TRun, TProvided>;
89
+ }
90
+
91
+ export interface StoryKit<TEngine, TStage extends string, TVerbs, TRun extends AnyEffect, TProvided> {
92
+ readonly it: StoryIt<TEngine, TStage, TVerbs, TRun, TProvided>;
93
+ readonly trait: <TEffect extends AnyEffect = never>(
94
+ stage: TStage,
95
+ line: string,
96
+ apply: Hook<[engine: TEngine], TEffect, void>,
97
+ ) => Trait<TEngine, TStage, ServicesOf<TEffect>>;
98
+ readonly traits: <const TGiven extends readonly Trait<TEngine, TStage, unknown>[]>(
99
+ ...given: TGiven
100
+ ) => Trait<TEngine, TStage, TraitServices<TGiven[number]>>;
101
+ }
102
+
103
+ interface LooseKit {
104
+ readonly it: LooseIt;
105
+ readonly trait: (stage: string, line: string, apply: Hook<[engine: unknown], AnyEffect, void>) => LooseTrait;
106
+ readonly traits: (...given: readonly LooseTrait[]) => LooseTrait;
107
+ }
108
+
109
+ // The implementation works on loose types; the overload states what the definition infers.
110
+ export function storyKit<
111
+ TEngine,
112
+ const TStage extends string,
113
+ TVerbs extends object,
114
+ TProvided = never,
115
+ TCreate extends AnyEffect = never,
116
+ TAfter extends { readonly [TKey in TStage]?: AnyEffect } = Record<never, never>,
117
+ TStep extends AnyEffect = never,
118
+ TFailed extends AnyEffect = never,
119
+ TDiagnose extends AnyEffect = never,
120
+ >(
121
+ definition: StoryKitDefinition<TEngine, TStage, TVerbs, TProvided, TCreate, TAfter, TStep, TFailed, TDiagnose> &
122
+ LayerFor<TCreate | AfterEffects<TAfter> | TStep | TFailed | TDiagnose, TProvided>,
123
+ ): StoryKit<TEngine, TStage, TVerbs, TStep | TFailed | TDiagnose, TProvided>;
124
+ export function storyKit(definition: LooseDefinition): LooseKit {
125
+ return {
126
+ it: storyIt(definition),
127
+ trait: (stage, line, apply) => trait(stage, line, (engine) => hooked(() => apply(engine))),
128
+ traits,
129
+ };
130
+ }
package/login.log DELETED
@@ -1,26 +0,0 @@
1
-
2
- https://www.npmjs.com/login?next=/login/cli/d0180614-d018-4dd0-9303-d9e68a5ed5a0
3
-
4
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
5
- █ ▄▄▄▄▄ █ █▄███▀█▄▀██▄▄ ▀█▄██ ▄▄▄▄▄ █
6
- █ █ █ █ ▀▄ █▄▀ ▄█▀▄▄▀▀ ▀▄ ██ █ █ █
7
- █ █▄▄▄█ █▀██▀▀ ▀▄▄▀▀▄█ ▄ █▄▄▄█ █▄▄▄█ █
8
- █▄▄▄▄▄▄▄█▄▀▄█ █ █▄▀ ▀▄▀ ▀▄█ ▀ █▄▄▄▄▄▄▄█
9
- █ ▄▀▀▄▄▄██▀▀▄▀▄▄▄▄▄▀▄▀ █▀▀█▀ ██▄▀▄ ▄█
10
- ██ ▄ ▄ ▄█▄▀▀ ▄ ▄ ▀ ██ █ ██▄ █▄▀▀ ▀▄▀█
11
- █ █▄▀ ▀▄█▄ ▄█▄ ▀█▀▄▄▄▀▄▄ ▀█▀▀▀▄▄ ▀▀ ▀▄█
12
- █▄█▀█▀▄▄▀ █▄█▀█▀█▄▀▄▀▄ ▀ ▄▄ ▄▄█ ▄▄▀▀▀██
13
- █ █▄█▀▀▄ ▄▀▀▄▀▄▄▄▄ ▄▄▀▄▀▀▀▀▀ ▀▄▄█▀▀ ▀▄█
14
- █▀ ▄▄ ▄▄█▄▀█ ▄ ▄ ▀ ██▄ █▄ ▄██▄█ █▀▀██
15
- █ ██▀▄▄▄██▀ █▄ ▀█▀▄▀█▀▄▀▀▀█▀ ▄█▄▄▀ ▄ ▄█
16
- ██ ▀█ ▄▄ ██▄█▀█▀█▄ ▀█ ▄ ▀█▀▄▄█ ▀█▀▀██
17
- █ ▄ ▀ ▄█▄█ ▄▀▄▄▄█▄▄█ ▀▀▄█▀ ▀▄▄▄▀ ▄ ▄█
18
- ███▀ ▀ ▄▀▄ ▄ ▄ ██ ▄ ▄▄ ▀▀▄███▀▀██
19
- █▄▄▄▄▄▄▄█ ▄██▄ ▀▀▄▄▀▄▀ █ █ ▄ ▄▄▄ ▄███
20
- █ ▄▄▄▄▄ █▀▀▄█▀█▀ ▀ █▀ █ ▄▄ █ █▄█ ▀▀▀██
21
- █ █ █ █▄ █▄▀▄▄▀▀▄▀▄▄▄▀ ▄▀▀▀ ▄▄▀ ▀ █
22
- █ █▄▄▄█ █▀ ▄ ▄ █ ▄█▀ ▀ ▀▄▀▀▄▄ ▄▀▄▀▀██
23
- █▄▄▄▄▄▄▄█▄▄▄█▄▄██▄▄▄▄▄▄▄▄▄██▄█▄████▄▄▄█
24
- 
25
- Press ENTER to open the URL in your browser.
26
- Logged in on https://registry.npmjs.org/
package/publish.log DELETED
File without changes
package/run.out DELETED
@@ -1,26 +0,0 @@
1
-
2
- https://www.npmjs.com/login?next=/login/cli/d0180614-d018-4dd0-9303-d9e68a5ed5a0
3
-
4
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
5
- █ ▄▄▄▄▄ █ █▄███▀█▄▀██▄▄ ▀█▄██ ▄▄▄▄▄ █
6
- █ █ █ █ ▀▄ █▄▀ ▄█▀▄▄▀▀ ▀▄ ██ █ █ █
7
- █ █▄▄▄█ █▀██▀▀ ▀▄▄▀▀▄█ ▄ █▄▄▄█ █▄▄▄█ █
8
- █▄▄▄▄▄▄▄█▄▀▄█ █ █▄▀ ▀▄▀ ▀▄█ ▀ █▄▄▄▄▄▄▄█
9
- █ ▄▀▀▄▄▄██▀▀▄▀▄▄▄▄▄▀▄▀ █▀▀█▀ ██▄▀▄ ▄█
10
- ██ ▄ ▄ ▄█▄▀▀ ▄ ▄ ▀ ██ █ ██▄ █▄▀▀ ▀▄▀█
11
- █ █▄▀ ▀▄█▄ ▄█▄ ▀█▀▄▄▄▀▄▄ ▀█▀▀▀▄▄ ▀▀ ▀▄█
12
- █▄█▀█▀▄▄▀ █▄█▀█▀█▄▀▄▀▄ ▀ ▄▄ ▄▄█ ▄▄▀▀▀██
13
- █ █▄█▀▀▄ ▄▀▀▄▀▄▄▄▄ ▄▄▀▄▀▀▀▀▀ ▀▄▄█▀▀ ▀▄█
14
- █▀ ▄▄ ▄▄█▄▀█ ▄ ▄ ▀ ██▄ █▄ ▄██▄█ █▀▀██
15
- █ ██▀▄▄▄██▀ █▄ ▀█▀▄▀█▀▄▀▀▀█▀ ▄█▄▄▀ ▄ ▄█
16
- ██ ▀█ ▄▄ ██▄█▀█▀█▄ ▀█ ▄ ▀█▀▄▄█ ▀█▀▀██
17
- █ ▄ ▀ ▄█▄█ ▄▀▄▄▄█▄▄█ ▀▀▄█▀ ▀▄▄▄▀ ▄ ▄█
18
- ███▀ ▀ ▄▀▄ ▄ ▄ ██ ▄ ▄▄ ▀▀▄███▀▀██
19
- █▄▄▄▄▄▄▄█ ▄██▄ ▀▀▄▄▀▄▀ █ █ ▄ ▄▄▄ ▄███
20
- █ ▄▄▄▄▄ █▀▀▄█▀█▀ ▀ █▀ █ ▄▄ █ █▄█ ▀▀▀██
21
- █ █ █ █▄ █▄▀▄▄▀▀▄▀▄▄▄▀ ▄▀▀▀ ▄▄▀ ▀ █
22
- █ █▄▄▄█ █▀ ▄ ▄ █ ▄█▀ ▀ ▀▄▀▀▄▄ ▄▀▄▀▀██
23
- █▄▄▄▄▄▄▄█▄▄▄█▄▄██▄▄▄▄▄▄▄▄▄██▄█▄████▄▄▄█
24
- 
25
- Press ENTER to open the URL in your browser.
26
- Logged in on https://registry.npmjs.org/
package/run.sh DELETED
@@ -1,10 +0,0 @@
1
- #!/bin/zsh
2
- cd "${0:A:h}"
3
- watch_open() { local log=$1 pat=$2; ( for i in {1..600}; do u=$(tr -d '\r' < "$log" 2>/dev/null | grep -oE "$pat" | tail -1); if [[ -n $u ]]; then open -g "$u"; break; fi; sleep 1; done ) & }
4
- : > login.log; watch_open login.log 'https://www\.npmjs\.com/login\?next=[A-Za-z0-9/=?._-]+'
5
- sleep 300 | script -q -F login.log pnpm login
6
- : > publish.log; watch_open publish.log 'https://www\.npmjs\.com/auth/cli/[A-Za-z0-9/=?._-]+'
7
- sleep 300 | script -q -F publish.log pnpm publish --access public --no-git-checks
8
- echo "publish exit $?"
9
- pnpm logout
10
- npm view @shivaedev/test-story versions