@by-association-only/harness-app 0.0.1-placeholder → 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.
- package/dist/branch-XrPxkmYQ.mjs +37 -0
- package/dist/function-tests.d.mts +7 -0
- package/dist/function-tests.mjs +41 -0
- package/dist/harness.d.mts +98 -0
- package/dist/harness.mjs +1382 -0
- package/dist/lint/index.d.mts +224 -0
- package/dist/lint/index.mjs +51 -0
- package/dist/lint/plugins/app.d.mts +4 -0
- package/dist/lint/plugins/app.mjs +35 -0
- package/dist/router.d.mts +12 -0
- package/dist/router.mjs +93 -0
- package/package.json +58 -1
- package/templates/docs/lore/branch-router.md +38 -0
- package/templates/docs/lore/codegen-checks-every-template.md +46 -0
- package/templates/docs/lore/files-through-intents.md +22 -0
- package/templates/docs/lore/one-deployment-per-client.md +25 -0
- package/templates/docs/lore/one-nav-list.md +36 -0
- package/templates/docs/lore/one-request-path.md +49 -0
- package/templates/docs/lore/polaris-2-rc.md +41 -0
- package/templates/docs/lore/saves-and-events.md +100 -0
- package/templates/docs/lore/shell-errors.md +49 -0
- package/templates/docs/lore/webhooks-are-ingress.md +57 -0
- package/templates/docs/manifesto.md +71 -0
- package/templates/skills/verify/admin-graphql.md +25 -0
- package/templates/skills/verify/building-admin-features.md +45 -0
- package/templates/skills/verify/embedded-admin.md +43 -0
- package/templates/skills/verify/when-the-app-grows.md +37 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { isBranchName } from "@by-association-only/harness-unisian/stages";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
3
|
+
import * as Data from "effect/Data";
|
|
4
|
+
//#region src/router/branch.ts
|
|
5
|
+
const branchCookie = "__Host-branch";
|
|
6
|
+
const branchParam = "branch";
|
|
7
|
+
const switchPath = "/__branch/";
|
|
8
|
+
const cookieMaxAgeSeconds = 604800;
|
|
9
|
+
const cookieAttributes = "Path=/; Secure; HttpOnly; SameSite=None; Partitioned";
|
|
10
|
+
const setBranchCookie = (branch) => `${branchCookie}=${branch}; ${cookieAttributes}; Max-Age=${cookieMaxAgeSeconds}`;
|
|
11
|
+
const clearBranchCookie = `${branchCookie}=; ${cookieAttributes}; Max-Age=0`;
|
|
12
|
+
const branchIn = (routerHost, value) => isBranchName(routerHost, value) ? Option.some(value) : Option.none();
|
|
13
|
+
const cookieBranch = (request, routerHost) => Option.fromNullishOr(request.headers.get("cookie")).pipe(Option.flatMap((header) => Option.fromNullishOr(header.split(";").map((pair) => pair.trim()).find((pair) => pair.startsWith(`${branchCookie}=`)))), Option.flatMap((pair) => branchIn(routerHost, pair.slice(14))));
|
|
14
|
+
const { Chosen, Default, Invalid, Switch } = Data.taggedEnum();
|
|
15
|
+
const chooseBranch = (request, routerHost) => {
|
|
16
|
+
const url = new URL(request.url);
|
|
17
|
+
const fromValue = (value, onValid) => Option.match(branchIn(routerHost, value), {
|
|
18
|
+
onNone: () => Invalid({ value }),
|
|
19
|
+
onSome: onValid
|
|
20
|
+
});
|
|
21
|
+
if (url.pathname.startsWith("/__branch/")) return fromValue(url.pathname.slice(10), (branch) => Switch({ branch }));
|
|
22
|
+
const param = url.searchParams.get(branchParam);
|
|
23
|
+
if (param === "") return Default({ setCookie: clearBranchCookie });
|
|
24
|
+
if (param !== null) return fromValue(param, (branch) => Chosen({
|
|
25
|
+
branch,
|
|
26
|
+
setCookie: setBranchCookie(branch)
|
|
27
|
+
}));
|
|
28
|
+
return Option.match(cookieBranch(request, routerHost), {
|
|
29
|
+
onNone: () => Default({ setCookie: null }),
|
|
30
|
+
onSome: (branch) => Chosen({
|
|
31
|
+
branch,
|
|
32
|
+
setCookie: null
|
|
33
|
+
})
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { switchPath as i, chooseBranch as n, setBranchCookie as r, branchCookie as t };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { readdirSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { buildFunction, getFunctionInfo, loadFixture, loadInputQuery, loadSchema, runFunction, validateTestAssets } from "@shopify/shopify-function-test-helpers";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
import { assert, beforeAll, describe, expect, test } from "vite-plus/test";
|
|
6
|
+
//#region src/function-tests.ts
|
|
7
|
+
const Targeting = Schema.Record(Schema.String, Schema.Struct({ inputQueryPath: Schema.String }));
|
|
8
|
+
const testFunctionFixtures = ({ instructionBudget, testsDir }) => {
|
|
9
|
+
if (!Number.isInteger(instructionBudget) || instructionBudget <= 0) throw new Error(`instructionBudget must be a whole number above 0, not ${instructionBudget}.`);
|
|
10
|
+
const functionDir = path.dirname(testsDir);
|
|
11
|
+
const fixturesDir = path.join(testsDir, "fixtures");
|
|
12
|
+
const fixtureFiles = readdirSync(fixturesDir).filter((file) => file.endsWith(".json")).toSorted();
|
|
13
|
+
if (fixtureFiles.length === 0) throw new Error(`${fixturesDir} has no .json fixture, so nothing tests this Function.`);
|
|
14
|
+
const built = {};
|
|
15
|
+
describe(path.basename(functionDir), () => {
|
|
16
|
+
beforeAll(async () => {
|
|
17
|
+
await buildFunction(functionDir);
|
|
18
|
+
built.info = await getFunctionInfo(functionDir);
|
|
19
|
+
}, 12e4);
|
|
20
|
+
test.each(fixtureFiles)(`runs %s within ${instructionBudget} instructions`, async (file) => {
|
|
21
|
+
const info = built.info ?? assert.fail("the Function wasn't built");
|
|
22
|
+
const fixture = await loadFixture(path.join(fixturesDir, file));
|
|
23
|
+
const inputQueryPath = Schema.decodeUnknownSync(Targeting)(info.targeting)[fixture.target]?.inputQueryPath ?? assert.fail(`the Function has no target ${fixture.target}`);
|
|
24
|
+
const validation = await validateTestAssets({
|
|
25
|
+
fixture,
|
|
26
|
+
inputQueryAST: await loadInputQuery(inputQueryPath),
|
|
27
|
+
schema: await loadSchema(info.schemaPath)
|
|
28
|
+
});
|
|
29
|
+
expect(validation.inputQuery.errors).toEqual([]);
|
|
30
|
+
expect(validation.inputFixture.errors).toEqual([]);
|
|
31
|
+
expect(validation.outputFixture.errors).toEqual([]);
|
|
32
|
+
const run = await runFunction(fixture, info.functionRunnerPath, info.wasmPath, inputQueryPath, info.schemaPath);
|
|
33
|
+
expect(run.error).toBeNull();
|
|
34
|
+
expect(run.result?.output).toEqual(fixture.expectedOutput);
|
|
35
|
+
const instructions = run.metadata?.instructionCount ?? assert.fail("function-runner reported no instruction count");
|
|
36
|
+
expect(instructions, `${file} ran ${instructions} instructions, over the Function's budget of ${instructionBudget}`).toBeLessThanOrEqual(instructionBudget);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
export { testFunctionFixtures };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import { Command } from "effect/cli";
|
|
3
|
+
import { ChildProcessSpawner } from "effect/process";
|
|
4
|
+
//#region src/codegen/check.d.ts
|
|
5
|
+
declare const CodegenCheckFailed_base: Schema.Class<CodegenCheckFailed, Schema.TaggedStruct<"CodegenCheckFailed", {
|
|
6
|
+
readonly reason: Schema.String;
|
|
7
|
+
}>, import("effect/Cause").YieldableError>;
|
|
8
|
+
declare class CodegenCheckFailed extends CodegenCheckFailed_base {
|
|
9
|
+
get message(): string;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/failed.d.ts
|
|
13
|
+
declare const CommandFailed_base: Schema.Class<CommandFailed, Schema.TaggedStruct<"CommandFailed", {
|
|
14
|
+
readonly reason: Schema.String;
|
|
15
|
+
}>, import("effect/Cause").YieldableError>;
|
|
16
|
+
declare class CommandFailed extends CommandFailed_base {
|
|
17
|
+
get message(): string;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/harness.d.ts
|
|
21
|
+
declare const _default: {
|
|
22
|
+
blocks: import("@by-association-only/harness/layer").LayerBlock[];
|
|
23
|
+
checkSteps: string[];
|
|
24
|
+
testSteps: string[];
|
|
25
|
+
commands: (import("effect/cli/Command").Command<"codegen-check", {}, {}, CodegenCheckFailed, import("effect/process/ChildProcessSpawner").ChildProcessSpawner> | import("effect/cli/Command").Command<"destroy", {}, {}, CommandFailed, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/FileSystem").FileSystem | import("effect/Path").Path> | import("effect/cli/Command").Command<"dev", {
|
|
26
|
+
readonly shopifyArgs: readonly string[];
|
|
27
|
+
}, {}, CommandFailed, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/FileSystem").FileSystem | import("effect/Path").Path> | import("effect/cli/Command").Command<"env-check", {}, {}, CommandFailed, import("effect/process/ChildProcessSpawner").ChildProcessSpawner> | import("effect/cli/Command").Command<"functions", {}, {}, CommandFailed, import("effect/process/ChildProcessSpawner").ChildProcessSpawner> | import("effect/cli/Command").Command<"pull-env", {}, {}, CommandFailed, import("effect/process/ChildProcessSpawner").ChildProcessSpawner> | import("effect/cli/Command").Command<"rust", {}, {}, CommandFailed, import("effect/process/ChildProcessSpawner").ChildProcessSpawner> | import("effect/cli/Command").Command<"slot", {}, {}, CommandFailed, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/FileSystem").FileSystem | import("effect/Path").Path> | import("effect/cli/Command").Command<"stage", {
|
|
28
|
+
readonly config: string;
|
|
29
|
+
}, {}, CommandFailed, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/FileSystem").FileSystem | import("effect/Path").Path>)[];
|
|
30
|
+
pieces: ({
|
|
31
|
+
blocks: {
|
|
32
|
+
file: string;
|
|
33
|
+
id: string;
|
|
34
|
+
slot: string;
|
|
35
|
+
text: string;
|
|
36
|
+
}[];
|
|
37
|
+
when: {
|
|
38
|
+
dependency: string;
|
|
39
|
+
file?: undefined;
|
|
40
|
+
};
|
|
41
|
+
} | {
|
|
42
|
+
blocks: {
|
|
43
|
+
file: string;
|
|
44
|
+
id: string;
|
|
45
|
+
slot: string;
|
|
46
|
+
text: string;
|
|
47
|
+
}[];
|
|
48
|
+
when: {
|
|
49
|
+
dependency?: undefined;
|
|
50
|
+
file: string;
|
|
51
|
+
};
|
|
52
|
+
})[];
|
|
53
|
+
verify: {
|
|
54
|
+
commands: import("effect/cli/Command").Command<string, {
|
|
55
|
+
readonly [x: string]: unknown;
|
|
56
|
+
}, {}, import("@by-association-only/harness/verify").EvidenceFailed | import("@by-association-only/harness/verify").VerifyFailed, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/FileSystem").FileSystem | import("effect/Path").Path>[];
|
|
57
|
+
doctor: import("effect/Effect").Effect<{
|
|
58
|
+
app: {
|
|
59
|
+
apiKey: string;
|
|
60
|
+
handle: string;
|
|
61
|
+
title: string;
|
|
62
|
+
};
|
|
63
|
+
shop: {
|
|
64
|
+
domain: string;
|
|
65
|
+
name: string;
|
|
66
|
+
};
|
|
67
|
+
apiVersion: string;
|
|
68
|
+
config: string;
|
|
69
|
+
devStore: string | null;
|
|
70
|
+
scopes: string[];
|
|
71
|
+
tokenExpiresAt: string;
|
|
72
|
+
evidence: string;
|
|
73
|
+
}, import("@by-association-only/harness/verify").EvidenceFailed | import("@by-association-only/harness/verify").VerifyFailed, import("effect/FileSystem").FileSystem | import("effect/Path").Path>;
|
|
74
|
+
doctorFlags: {
|
|
75
|
+
app: string;
|
|
76
|
+
shop: string;
|
|
77
|
+
};
|
|
78
|
+
settings: {
|
|
79
|
+
refusedFramingHint: string;
|
|
80
|
+
};
|
|
81
|
+
steps: {
|
|
82
|
+
branch: import("@by-association-only/harness/verify").FlowStep<{
|
|
83
|
+
readonly app: import("effect/Schema").optional<import("effect/Schema").String>;
|
|
84
|
+
readonly headed: import("effect/Schema").optional<import("effect/Schema").Boolean>;
|
|
85
|
+
readonly name: import("effect/Schema").String;
|
|
86
|
+
}, import("effect/FileSystem").FileSystem | import("effect/Path").Path>;
|
|
87
|
+
};
|
|
88
|
+
surfaces: import("effect/Effect").Effect<string[], import("effect/PlatformError").BadArgument | {
|
|
89
|
+
message: string;
|
|
90
|
+
}, import("effect/FileSystem").FileSystem | import("effect/Path").Path>;
|
|
91
|
+
};
|
|
92
|
+
worktree: {
|
|
93
|
+
postCreate: string[];
|
|
94
|
+
preRemove: string[];
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
//#endregion
|
|
98
|
+
export { _default as default };
|