@hitch42/applet 0.1.0-beta.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.
- package/LICENSE +21 -0
- package/dist/_internal/client.d.ts +7 -0
- package/dist/_internal/client.js +60 -0
- package/dist/_internal/server.d.ts +79 -0
- package/dist/_internal/server.js +232 -0
- package/dist/applet.d.ts +1 -0
- package/dist/applet.js +160 -0
- package/dist/auth-DaVK0k5R.js +350 -0
- package/dist/batch-Qa4B4-Yp.d.ts +30 -0
- package/dist/build-BRkmrUcj.js +74 -0
- package/dist/build-CMluoV6q.js +2 -0
- package/dist/client.d.ts +48 -0
- package/dist/client.js +38 -0
- package/dist/contract-DMrpbaLJ.d.ts +23 -0
- package/dist/deploy-DKB-Tef7.js +2416 -0
- package/dist/generate-BFmIzHaR.js +484 -0
- package/dist/generate-CefnB-bp.js +2 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.js +136 -0
- package/dist/init-CQRsnd3i.js +240 -0
- package/dist/lazy-client-x0DCI98S.js +64 -0
- package/dist/load-config-BnxocLAQ.js +56 -0
- package/dist/logs-m00pzr_O.js +183 -0
- package/dist/manifest-BQCAxATS.d.ts +81 -0
- package/dist/manifest-DyoJ1MIL.js +174 -0
- package/dist/project-CEmDnlKa.js +346 -0
- package/dist/react.d.ts +37 -0
- package/dist/react.js +517 -0
- package/dist/server.d.ts +46 -0
- package/dist/server.js +104 -0
- package/dist/upload-DRbVhuDm.js +114 -0
- package/dist/upload-YbRKEqR6.d.ts +112 -0
- package/dist/variables-CP7Ce3Np.js +133 -0
- package/dist/vite.d.ts +63 -0
- package/dist/vite.js +358 -0
- package/dist/workflow-CTo2kZF1.js +51 -0
- package/dist/workflow-DurM6Fwx.d.ts +28 -0
- package/package.json +104 -0
- package/scaffold/.agents/skills/hitch-applets/SKILL.md +137 -0
- package/scaffold/.agents/skills/hitch-applets/references/client.md +73 -0
- package/scaffold/.agents/skills/hitch-applets/references/files.md +109 -0
- package/scaffold/.agents/skills/hitch-applets/references/manifest.md +171 -0
- package/scaffold/.agents/skills/hitch-applets/references/markdown.md +37 -0
- package/scaffold/.agents/skills/hitch-applets/references/records.md +152 -0
- package/scaffold/.agents/skills/hitch-applets/references/schedules.md +86 -0
- package/scaffold/.agents/skills/hitch-applets/references/server.md +98 -0
- package/scaffold/.agents/skills/hitch-applets/references/workflows.md +73 -0
- package/scaffold/AGENTS.md +38 -0
- package/scaffold/applet/components.json +25 -0
- package/scaffold/applet/src/client/components/ui/alert.tsx +73 -0
- package/scaffold/applet/src/client/components/ui/avatar.tsx +100 -0
- package/scaffold/applet/src/client/components/ui/badge.tsx +55 -0
- package/scaffold/applet/src/client/components/ui/button.tsx +90 -0
- package/scaffold/applet/src/client/components/ui/card.tsx +85 -0
- package/scaffold/applet/src/client/components/ui/checkbox.tsx +35 -0
- package/scaffold/applet/src/client/components/ui/dialog.tsx +164 -0
- package/scaffold/applet/src/client/components/ui/dropdown-menu.tsx +235 -0
- package/scaffold/applet/src/client/components/ui/input-group.tsx +144 -0
- package/scaffold/applet/src/client/components/ui/input.tsx +22 -0
- package/scaffold/applet/src/client/components/ui/label.tsx +29 -0
- package/scaffold/applet/src/client/components/ui/popover.tsx +74 -0
- package/scaffold/applet/src/client/components/ui/select.tsx +243 -0
- package/scaffold/applet/src/client/components/ui/separator.tsx +25 -0
- package/scaffold/applet/src/client/components/ui/sheet.tsx +177 -0
- package/scaffold/applet/src/client/components/ui/skeleton.tsx +13 -0
- package/scaffold/applet/src/client/components/ui/spinner.tsx +16 -0
- package/scaffold/applet/src/client/components/ui/switch.tsx +41 -0
- package/scaffold/applet/src/client/components/ui/table.tsx +114 -0
- package/scaffold/applet/src/client/components/ui/tabs.tsx +78 -0
- package/scaffold/applet/src/client/components/ui/textarea.tsx +23 -0
- package/scaffold/applet/src/client/lib/utils.ts +6 -0
- package/scaffold/applet/src/client/styles/globals.css +136 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { t as AnyWorkflowDefinition } from "./workflow-DurM6Fwx.js";
|
|
2
|
+
import { AnyProcedure, AnyRouter, Lazy } from "@orpc/server";
|
|
3
|
+
import { AnyContractRouter } from "@orpc/contract";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
//#region src/hitch/registry.d.ts
|
|
6
|
+
interface HitchRegister {}
|
|
7
|
+
type RegisteredCatalog = HitchRegister extends {
|
|
8
|
+
catalog: infer C;
|
|
9
|
+
} ? C : Record<string, string>;
|
|
10
|
+
type RegisteredContract = HitchRegister extends {
|
|
11
|
+
contract: infer C extends AnyContractRouter;
|
|
12
|
+
} ? C : AnyContractRouter;
|
|
13
|
+
type RegisteredManifest = HitchRegister extends {
|
|
14
|
+
manifest: infer M extends AppletManifest;
|
|
15
|
+
} ? M : AppletManifest;
|
|
16
|
+
type RegisteredVariables = HitchRegister extends {
|
|
17
|
+
manifest: infer M extends AppletManifest;
|
|
18
|
+
} ? M extends {
|
|
19
|
+
readonly variables: infer Variables extends NonNullable<AppletManifest["variables"]>;
|
|
20
|
+
} ? Variables : {} : {};
|
|
21
|
+
type RegisteredVariableKeys = keyof RegisteredVariables;
|
|
22
|
+
type RegisteredRouter = HitchRegister extends {
|
|
23
|
+
router: infer R extends AnyRouter;
|
|
24
|
+
} ? R : AnyRouter;
|
|
25
|
+
type RegisteredWorkflows = HitchRegister extends {
|
|
26
|
+
workflows: infer W extends Record<string, AnyWorkflowDefinition>;
|
|
27
|
+
} ? W : Record<string, AnyWorkflowDefinition>;
|
|
28
|
+
declare function registerHitchContract(contract: AnyContractRouter): void;
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/manifest.d.ts
|
|
31
|
+
interface AppletManifest {
|
|
32
|
+
org: string;
|
|
33
|
+
name: string;
|
|
34
|
+
code: string;
|
|
35
|
+
schedules?: readonly {
|
|
36
|
+
code: string;
|
|
37
|
+
name: string;
|
|
38
|
+
cron: string;
|
|
39
|
+
path: string;
|
|
40
|
+
}[];
|
|
41
|
+
variables?: Readonly<Record<string, {
|
|
42
|
+
secret?: boolean;
|
|
43
|
+
optional?: boolean;
|
|
44
|
+
}>>;
|
|
45
|
+
objects: readonly {
|
|
46
|
+
code: string;
|
|
47
|
+
canCreate: boolean;
|
|
48
|
+
canDelete: boolean;
|
|
49
|
+
readOnly: readonly string[];
|
|
50
|
+
readWrite: readonly string[];
|
|
51
|
+
}[];
|
|
52
|
+
}
|
|
53
|
+
type AppletDefinition = Omit<AppletManifest, "org">;
|
|
54
|
+
type AppletManifestObject = AppletManifest["objects"][number];
|
|
55
|
+
interface AppletPackages {
|
|
56
|
+
dependencies?: Readonly<Record<string, string>>;
|
|
57
|
+
devDependencies?: Readonly<Record<string, string>>;
|
|
58
|
+
}
|
|
59
|
+
type AppletSourceDefinition = AppletDefinition & AppletPackages;
|
|
60
|
+
declare const appletManifestSchema: z.ZodType<AppletManifest>;
|
|
61
|
+
declare const appletDefinitionSchema: z.ZodType<AppletDefinition>;
|
|
62
|
+
declare const appletSourceDefinitionSchema: z.ZodType<AppletSourceDefinition>;
|
|
63
|
+
type RegisteredAppletManifestObject = { [Code in keyof RegisteredCatalog]: Omit<AppletManifestObject, "code" | "readOnly" | "readWrite"> & {
|
|
64
|
+
code: Code;
|
|
65
|
+
readOnly: readonly RegisteredCatalog[Code][];
|
|
66
|
+
readWrite: readonly RegisteredCatalog[Code][];
|
|
67
|
+
}; }[keyof RegisteredCatalog];
|
|
68
|
+
type RouterSchedulePaths<R, Prefix extends string = ""> = R extends AnyProcedure ? Prefix : R extends Lazy<infer U> ? RouterSchedulePaths<U, Prefix> : { [K in keyof R & string]: RouterSchedulePaths<R[K], `${Prefix}/${K}`>; }[keyof R & string];
|
|
69
|
+
type RegisteredSchedulePath = HitchRegister extends {
|
|
70
|
+
router: infer R extends AnyRouter;
|
|
71
|
+
} ? RouterSchedulePaths<R> : string;
|
|
72
|
+
type RegisteredAppletManifest = Omit<AppletManifest, "objects" | "schedules"> & {
|
|
73
|
+
schedules?: readonly (Omit<NonNullable<AppletManifest["schedules"]>[number], "path"> & {
|
|
74
|
+
path: RegisteredSchedulePath;
|
|
75
|
+
})[];
|
|
76
|
+
objects: readonly RegisteredAppletManifestObject[];
|
|
77
|
+
};
|
|
78
|
+
type RegisteredAppletDefinition = Omit<RegisteredAppletManifest, "org">;
|
|
79
|
+
type RegisteredAppletSourceDefinition = RegisteredAppletDefinition & AppletPackages;
|
|
80
|
+
//#endregion
|
|
81
|
+
export { RegisteredWorkflows as _, AppletSourceDefinition as a, appletManifestSchema as c, RegisteredCatalog as d, RegisteredContract as f, RegisteredVariables as g, RegisteredVariableKeys as h, AppletPackages as i, appletSourceDefinitionSchema as l, RegisteredRouter as m, AppletManifest as n, RegisteredAppletSourceDefinition as o, RegisteredManifest as p, AppletManifestObject as r, appletDefinitionSchema as s, AppletDefinition as t, HitchRegister as u, registerHitchContract as v };
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Cron } from "croner";
|
|
3
|
+
//#region src/manifest-patterns.ts
|
|
4
|
+
const APPLET_CODE_PATTERN = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/;
|
|
5
|
+
const ORG_CODE_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/manifest.ts
|
|
8
|
+
const RESERVED_ATTRIBUTE_CODES = /* @__PURE__ */ new Set([
|
|
9
|
+
"id",
|
|
10
|
+
"name",
|
|
11
|
+
"expected_version"
|
|
12
|
+
]);
|
|
13
|
+
const codeSchema = z.string().regex(APPLET_CODE_PATTERN, "must start with a lowercase letter, end with a letter or number, and contain only lowercase letters, numbers, and non-consecutive underscores");
|
|
14
|
+
const nameSchema = z.string().trim().min(1).max(120);
|
|
15
|
+
const attributeCodeSchema = codeSchema.superRefine((code, context) => {
|
|
16
|
+
if (RESERVED_ATTRIBUTE_CODES.has(code)) context.addIssue({
|
|
17
|
+
code: "custom",
|
|
18
|
+
message: `Reserved record field code "${code}" is not allowed.`
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
const attributeCodesSchema = z.array(attributeCodeSchema).superRefine((codes, context) => {
|
|
22
|
+
const duplicateIndex = codes.findIndex((code, index) => codes.indexOf(code) !== index);
|
|
23
|
+
if (duplicateIndex !== -1) context.addIssue({
|
|
24
|
+
code: "custom",
|
|
25
|
+
message: `Duplicate attribute code "${codes[duplicateIndex]}".`,
|
|
26
|
+
path: [duplicateIndex]
|
|
27
|
+
});
|
|
28
|
+
}).readonly();
|
|
29
|
+
const appletManifestObjectSchema = z.strictObject({
|
|
30
|
+
code: codeSchema,
|
|
31
|
+
canCreate: z.boolean(),
|
|
32
|
+
canDelete: z.boolean(),
|
|
33
|
+
readOnly: attributeCodesSchema,
|
|
34
|
+
readWrite: attributeCodesSchema
|
|
35
|
+
}).superRefine((object, context) => {
|
|
36
|
+
const overlap = object.readOnly.find((code) => object.readWrite.includes(code));
|
|
37
|
+
if (overlap !== void 0) context.addIssue({
|
|
38
|
+
code: "custom",
|
|
39
|
+
message: `readOnly and readWrite both contain "${overlap}".`
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
const appletManifestObjectsSchema = z.array(appletManifestObjectSchema).readonly();
|
|
43
|
+
const appletManifestSchedulesSchema = z.array(z.strictObject({
|
|
44
|
+
code: codeSchema,
|
|
45
|
+
name: nameSchema,
|
|
46
|
+
cron: z.string().refine((value) => {
|
|
47
|
+
if (value.trim().split(/\s+/).length !== 5) return false;
|
|
48
|
+
try {
|
|
49
|
+
new Cron(value);
|
|
50
|
+
return true;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}, "must be a valid 5-field cron expression (minute hour day-of-month month day-of-week)"),
|
|
55
|
+
path: z.string().startsWith("/", "must start with \"/\"").refine((path) => !path.startsWith("//"), "must not start with \"//\"").refine((path) => !/\s/.test(path), "must not contain whitespace")
|
|
56
|
+
})).readonly();
|
|
57
|
+
const appletVariableKeySchema = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).refine((key) => !/^hitch_/i.test(key), { message: "Keys with the HITCH_ prefix are reserved." }).max(128);
|
|
58
|
+
const appletManifestVariablesSchema = z.record(appletVariableKeySchema, z.strictObject({
|
|
59
|
+
secret: z.boolean().optional(),
|
|
60
|
+
optional: z.boolean().optional()
|
|
61
|
+
})).readonly();
|
|
62
|
+
const appletManifestShape = {
|
|
63
|
+
org: z.string().regex(ORG_CODE_PATTERN, "must start with a lowercase letter, end with a letter or number, and contain only lowercase letters, numbers, and non-consecutive hyphens"),
|
|
64
|
+
name: nameSchema,
|
|
65
|
+
code: codeSchema,
|
|
66
|
+
schedules: appletManifestSchedulesSchema.optional(),
|
|
67
|
+
variables: appletManifestVariablesSchema.optional(),
|
|
68
|
+
objects: appletManifestObjectsSchema
|
|
69
|
+
};
|
|
70
|
+
const checkDuplicateCodes = (manifest, context) => {
|
|
71
|
+
const duplicateIndex = manifest.objects.findIndex(({ code }, index) => manifest.objects.findIndex((object) => object.code === code) !== index);
|
|
72
|
+
if (duplicateIndex !== -1) context.addIssue({
|
|
73
|
+
code: "custom",
|
|
74
|
+
message: `Duplicate object code "${manifest.objects[duplicateIndex]?.code}".`,
|
|
75
|
+
path: [
|
|
76
|
+
"objects",
|
|
77
|
+
duplicateIndex,
|
|
78
|
+
"code"
|
|
79
|
+
]
|
|
80
|
+
});
|
|
81
|
+
const schedules = manifest.schedules ?? [];
|
|
82
|
+
const duplicateScheduleIndex = schedules.findIndex(({ code }, index) => schedules.findIndex((schedule) => schedule.code === code) !== index);
|
|
83
|
+
if (duplicateScheduleIndex !== -1) context.addIssue({
|
|
84
|
+
code: "custom",
|
|
85
|
+
message: `Duplicate schedule code "${schedules[duplicateScheduleIndex]?.code}".`,
|
|
86
|
+
path: [
|
|
87
|
+
"schedules",
|
|
88
|
+
duplicateScheduleIndex,
|
|
89
|
+
"code"
|
|
90
|
+
]
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
const appletManifestSchema = z.strictObject(appletManifestShape).superRefine(checkDuplicateCodes);
|
|
94
|
+
const appletDefinitionSchema = z.strictObject({
|
|
95
|
+
name: appletManifestShape.name,
|
|
96
|
+
code: appletManifestShape.code,
|
|
97
|
+
schedules: appletManifestShape.schedules,
|
|
98
|
+
variables: appletManifestShape.variables,
|
|
99
|
+
objects: appletManifestShape.objects
|
|
100
|
+
}).superRefine(checkDuplicateCodes);
|
|
101
|
+
const packageVersionsSchema = z.record(z.string().min(1), z.string().min(1)).readonly();
|
|
102
|
+
const RESERVED_APPLET_PACKAGES = /* @__PURE__ */ new Set([
|
|
103
|
+
"@fontsource-variable/inter",
|
|
104
|
+
"@hitch42/applet",
|
|
105
|
+
"@orpc/contract",
|
|
106
|
+
"@orpc/server",
|
|
107
|
+
"@tailwindcss/vite",
|
|
108
|
+
"@tanstack/react-query",
|
|
109
|
+
"@tanstack/react-router",
|
|
110
|
+
"@tiptap/core",
|
|
111
|
+
"@tiptap/extension-image",
|
|
112
|
+
"@tiptap/extension-list",
|
|
113
|
+
"@tiptap/extension-table",
|
|
114
|
+
"@tiptap/extensions",
|
|
115
|
+
"@tiptap/markdown",
|
|
116
|
+
"@tiptap/react",
|
|
117
|
+
"@tiptap/starter-kit",
|
|
118
|
+
"@types/node",
|
|
119
|
+
"@types/react",
|
|
120
|
+
"@types/react-dom",
|
|
121
|
+
"class-variance-authority",
|
|
122
|
+
"clsx",
|
|
123
|
+
"lucide-react",
|
|
124
|
+
"react",
|
|
125
|
+
"react-aria-components",
|
|
126
|
+
"react-dom",
|
|
127
|
+
"shadcn",
|
|
128
|
+
"streamdown",
|
|
129
|
+
"tailwind-merge",
|
|
130
|
+
"tailwindcss",
|
|
131
|
+
"tw-animate-css",
|
|
132
|
+
"typescript",
|
|
133
|
+
"vite",
|
|
134
|
+
"vitest",
|
|
135
|
+
"zod"
|
|
136
|
+
]);
|
|
137
|
+
const checkSourcePackages = (manifest, context) => {
|
|
138
|
+
const dependencies = Object.keys(manifest.dependencies ?? {});
|
|
139
|
+
const devDependencies = new Set(Object.keys(manifest.devDependencies ?? {}));
|
|
140
|
+
[{
|
|
141
|
+
field: "dependencies",
|
|
142
|
+
names: dependencies
|
|
143
|
+
}, {
|
|
144
|
+
field: "devDependencies",
|
|
145
|
+
names: [...devDependencies]
|
|
146
|
+
}].forEach(({ field, names }) => names.forEach((name) => {
|
|
147
|
+
if (RESERVED_APPLET_PACKAGES.has(name)) context.addIssue({
|
|
148
|
+
code: "custom",
|
|
149
|
+
message: `Package "${name}" is managed by the applet CLI.`,
|
|
150
|
+
path: [field, name]
|
|
151
|
+
});
|
|
152
|
+
}));
|
|
153
|
+
dependencies.filter((name) => devDependencies.has(name)).forEach((name) => {
|
|
154
|
+
context.addIssue({
|
|
155
|
+
code: "custom",
|
|
156
|
+
message: `Package "${name}" cannot be in both dependencies and devDependencies.`,
|
|
157
|
+
path: ["devDependencies", name]
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
const appletSourceDefinitionSchema = z.strictObject({
|
|
162
|
+
name: appletManifestShape.name,
|
|
163
|
+
code: appletManifestShape.code,
|
|
164
|
+
schedules: appletManifestShape.schedules,
|
|
165
|
+
variables: appletManifestShape.variables,
|
|
166
|
+
objects: appletManifestShape.objects,
|
|
167
|
+
dependencies: packageVersionsSchema.optional(),
|
|
168
|
+
devDependencies: packageVersionsSchema.optional()
|
|
169
|
+
}).superRefine((manifest, context) => {
|
|
170
|
+
checkDuplicateCodes(manifest, context);
|
|
171
|
+
checkSourcePackages(manifest, context);
|
|
172
|
+
});
|
|
173
|
+
//#endregion
|
|
174
|
+
export { ORG_CODE_PATTERN as a, APPLET_CODE_PATTERN as i, appletManifestSchema as n, appletSourceDefinitionSchema as r, appletDefinitionSchema as t };
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { n as loadAppletSourceDefinition, r as readWorkspaceConfig } from "./load-config-BnxocLAQ.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { cp, mkdir, readFile, rm, symlink, writeFile } from "node:fs/promises";
|
|
5
|
+
import { dirname, relative, resolve } from "node:path";
|
|
6
|
+
//#region src/project.ts
|
|
7
|
+
const PACKAGE_MANAGER = "pnpm@11.18.0+sha512.33d83c77da82f49fba836925c6f1b841181ec3132b670639bd012f7075f5c7cf634c5f870147c19aae7478fac01df09d8892e880454896edd23ee9b33757563c";
|
|
8
|
+
const cliManifestValueSchema = z.string().min(1, "must be a non-empty string");
|
|
9
|
+
const cliManifestSchema = z.object({
|
|
10
|
+
name: cliManifestValueSchema,
|
|
11
|
+
version: cliManifestValueSchema,
|
|
12
|
+
dependencies: z.object({
|
|
13
|
+
"@fontsource-variable/inter": cliManifestValueSchema,
|
|
14
|
+
"@orpc/contract": cliManifestValueSchema,
|
|
15
|
+
"@tiptap/core": cliManifestValueSchema,
|
|
16
|
+
"@tiptap/extension-image": cliManifestValueSchema,
|
|
17
|
+
"@tiptap/extension-list": cliManifestValueSchema,
|
|
18
|
+
"@tiptap/extension-table": cliManifestValueSchema,
|
|
19
|
+
"@tiptap/extensions": cliManifestValueSchema,
|
|
20
|
+
"@tiptap/markdown": cliManifestValueSchema,
|
|
21
|
+
"@tiptap/react": cliManifestValueSchema,
|
|
22
|
+
"@tiptap/starter-kit": cliManifestValueSchema,
|
|
23
|
+
"@tailwindcss/vite": cliManifestValueSchema,
|
|
24
|
+
"@tanstack/router-cli": cliManifestValueSchema,
|
|
25
|
+
"class-variance-authority": cliManifestValueSchema,
|
|
26
|
+
clsx: cliManifestValueSchema,
|
|
27
|
+
"lucide-react": cliManifestValueSchema,
|
|
28
|
+
"react-aria-components": cliManifestValueSchema,
|
|
29
|
+
shadcn: cliManifestValueSchema,
|
|
30
|
+
streamdown: cliManifestValueSchema,
|
|
31
|
+
"tailwind-merge": cliManifestValueSchema,
|
|
32
|
+
tailwindcss: cliManifestValueSchema,
|
|
33
|
+
"tw-animate-css": cliManifestValueSchema,
|
|
34
|
+
vite: cliManifestValueSchema,
|
|
35
|
+
vitest: cliManifestValueSchema,
|
|
36
|
+
zod: cliManifestValueSchema
|
|
37
|
+
}),
|
|
38
|
+
peerDependencies: z.object({
|
|
39
|
+
"@orpc/server": cliManifestValueSchema,
|
|
40
|
+
"@tanstack/react-query": cliManifestValueSchema,
|
|
41
|
+
"@tanstack/react-router": cliManifestValueSchema,
|
|
42
|
+
"@types/node": cliManifestValueSchema,
|
|
43
|
+
"@types/react": cliManifestValueSchema,
|
|
44
|
+
"@types/react-dom": cliManifestValueSchema,
|
|
45
|
+
react: cliManifestValueSchema,
|
|
46
|
+
"react-dom": cliManifestValueSchema,
|
|
47
|
+
typescript: cliManifestValueSchema
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
async function loadCliManifest(options) {
|
|
51
|
+
const result = cliManifestSchema.safeParse(JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")));
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
const issue = result.error.issues[0];
|
|
54
|
+
if (issue === void 0) throw new Error("Invalid CLI package manifest.");
|
|
55
|
+
throw new Error(`Invalid CLI package manifest field "${issue.path.join(".")}": ${issue.message}.`);
|
|
56
|
+
}
|
|
57
|
+
const catalogEntry = [...Object.entries(result.data.dependencies).map(([name, value]) => [`dependencies.${name}`, value]), ...Object.entries(result.data.peerDependencies).map(([name, value]) => [`peerDependencies.${name}`, value])].find(([, value]) => value?.startsWith("catalog:"));
|
|
58
|
+
if (catalogEntry !== void 0) {
|
|
59
|
+
if (options?.allowDevelopment === true) return void 0;
|
|
60
|
+
throw new Error(`Invalid CLI package manifest field "${catalogEntry[0]}": "catalog:" identifies an unpublished development build. Run the published @hitch42/applet CLI instead.`);
|
|
61
|
+
}
|
|
62
|
+
return result.data;
|
|
63
|
+
}
|
|
64
|
+
function assertWorkspaceCli(config, cli) {
|
|
65
|
+
const runningCli = `${cli.name}@${cli.version}`;
|
|
66
|
+
if (config.sdk !== runningCli) throw new Error(`This workspace requires ${config.sdk}, but this command uses ${runningCli}. Run pnpm dlx ${config.sdk} bootstrap.`);
|
|
67
|
+
}
|
|
68
|
+
function workspacePackage(org) {
|
|
69
|
+
return {
|
|
70
|
+
name: `${org.replaceAll("_", "-")}-applets`,
|
|
71
|
+
private: true,
|
|
72
|
+
type: "module",
|
|
73
|
+
devDependencies: {
|
|
74
|
+
"@hitch42/applet": "catalog:",
|
|
75
|
+
typescript: "catalog:",
|
|
76
|
+
vite: "catalog:",
|
|
77
|
+
vitest: "catalog:"
|
|
78
|
+
},
|
|
79
|
+
packageManager: PACKAGE_MANAGER
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function workspaceFile(cli) {
|
|
83
|
+
return `packages:
|
|
84
|
+
- applets/*
|
|
85
|
+
catalog:
|
|
86
|
+
"@hitch42/applet": npm:${cli.name}@${cli.version}
|
|
87
|
+
"@fontsource-variable/inter": "${cli.dependencies["@fontsource-variable/inter"]}"
|
|
88
|
+
"@orpc/contract": "${cli.dependencies["@orpc/contract"]}"
|
|
89
|
+
"@orpc/server": "${cli.peerDependencies["@orpc/server"]}"
|
|
90
|
+
"@tiptap/core": "${cli.dependencies["@tiptap/core"]}"
|
|
91
|
+
"@tiptap/extension-image": "${cli.dependencies["@tiptap/extension-image"]}"
|
|
92
|
+
"@tiptap/extension-list": "${cli.dependencies["@tiptap/extension-list"]}"
|
|
93
|
+
"@tiptap/extension-table": "${cli.dependencies["@tiptap/extension-table"]}"
|
|
94
|
+
"@tiptap/extensions": "${cli.dependencies["@tiptap/extensions"]}"
|
|
95
|
+
"@tiptap/markdown": "${cli.dependencies["@tiptap/markdown"]}"
|
|
96
|
+
"@tiptap/react": "${cli.dependencies["@tiptap/react"]}"
|
|
97
|
+
"@tiptap/starter-kit": "${cli.dependencies["@tiptap/starter-kit"]}"
|
|
98
|
+
"@tanstack/react-query": "${cli.peerDependencies["@tanstack/react-query"]}"
|
|
99
|
+
"@tanstack/react-router": "${cli.peerDependencies["@tanstack/react-router"]}"
|
|
100
|
+
"@tanstack/router-cli": "${cli.dependencies["@tanstack/router-cli"]}"
|
|
101
|
+
"@types/node": "${cli.peerDependencies["@types/node"]}"
|
|
102
|
+
"@types/react": "${cli.peerDependencies["@types/react"]}"
|
|
103
|
+
"@types/react-dom": "${cli.peerDependencies["@types/react-dom"]}"
|
|
104
|
+
"@tailwindcss/vite": "${cli.dependencies["@tailwindcss/vite"]}"
|
|
105
|
+
class-variance-authority: "${cli.dependencies["class-variance-authority"]}"
|
|
106
|
+
clsx: "${cli.dependencies.clsx}"
|
|
107
|
+
lucide-react: "${cli.dependencies["lucide-react"]}"
|
|
108
|
+
react: "${cli.peerDependencies.react}"
|
|
109
|
+
react-aria-components: "${cli.dependencies["react-aria-components"]}"
|
|
110
|
+
react-dom: "${cli.peerDependencies["react-dom"]}"
|
|
111
|
+
shadcn: "${cli.dependencies.shadcn}"
|
|
112
|
+
streamdown: "${cli.dependencies.streamdown}"
|
|
113
|
+
tailwind-merge: "${cli.dependencies["tailwind-merge"]}"
|
|
114
|
+
tailwindcss: "${cli.dependencies.tailwindcss}"
|
|
115
|
+
tw-animate-css: "${cli.dependencies["tw-animate-css"]}"
|
|
116
|
+
typescript: "${cli.peerDependencies.typescript}"
|
|
117
|
+
vite: "${cli.dependencies.vite}"
|
|
118
|
+
vitest: "${cli.dependencies.vitest}"
|
|
119
|
+
zod: "${cli.dependencies.zod}"
|
|
120
|
+
allowBuilds:
|
|
121
|
+
esbuild: false
|
|
122
|
+
workerd: true
|
|
123
|
+
minimumReleaseAge: 4320
|
|
124
|
+
minimumReleaseAgeExclude:
|
|
125
|
+
- "@hitch42/*"
|
|
126
|
+
`;
|
|
127
|
+
}
|
|
128
|
+
const GITIGNORE_START = "# BEGIN Hitch generated files";
|
|
129
|
+
const GITIGNORE_END = "# END Hitch generated files";
|
|
130
|
+
const GITIGNORE = `${GITIGNORE_START}
|
|
131
|
+
/.agents/skills/hitch-applets/
|
|
132
|
+
/.node-version
|
|
133
|
+
/AGENTS.md
|
|
134
|
+
/CLAUDE.md
|
|
135
|
+
/package.json
|
|
136
|
+
/pnpm-lock.yaml
|
|
137
|
+
/pnpm-workspace.yaml
|
|
138
|
+
/applets/**/components.json
|
|
139
|
+
/applets/**/index.html
|
|
140
|
+
/applets/**/package.json
|
|
141
|
+
/applets/**/tsconfig.json
|
|
142
|
+
/applets/**/tsr.config.json
|
|
143
|
+
/applets/**/vite.config.ts
|
|
144
|
+
**/.hitch/
|
|
145
|
+
**/.hitch.tmp-*/
|
|
146
|
+
**/.tanstack/
|
|
147
|
+
**/.wrangler/
|
|
148
|
+
**/dist/
|
|
149
|
+
**/node_modules/
|
|
150
|
+
${GITIGNORE_END}
|
|
151
|
+
`;
|
|
152
|
+
async function writeGitignore(root) {
|
|
153
|
+
const path = resolve(root, ".gitignore");
|
|
154
|
+
if (!existsSync(path)) {
|
|
155
|
+
await writeFile(path, `${GITIGNORE}\n# Add custom rules below.\n`);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const current = await readFile(path, "utf8");
|
|
159
|
+
const start = current.indexOf(GITIGNORE_START);
|
|
160
|
+
const end = current.indexOf(GITIGNORE_END, start);
|
|
161
|
+
if (start >= 0 && end >= 0) {
|
|
162
|
+
const suffix = current.slice(end + 27).replace(/^\r?\n/, "");
|
|
163
|
+
await writeFile(path, `${current.slice(0, start)}${GITIGNORE}${suffix}`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
await writeFile(path, `${GITIGNORE}\n${current}`);
|
|
167
|
+
}
|
|
168
|
+
async function writeWorkspaceSupport(root, config, cli) {
|
|
169
|
+
const resolvedCli = cli ?? await loadCliManifest();
|
|
170
|
+
if (resolvedCli === void 0) throw new Error("The CLI package manifest is not published.");
|
|
171
|
+
assertWorkspaceCli(config, resolvedCli);
|
|
172
|
+
const skillTarget = resolve(root, ".agents/skills/hitch-applets");
|
|
173
|
+
const agents = await readFile(new URL("../scaffold/AGENTS.md", import.meta.url), "utf8");
|
|
174
|
+
await Promise.all([mkdir(resolve(root, ".agents/skills"), { recursive: true }), rm(skillTarget, {
|
|
175
|
+
force: true,
|
|
176
|
+
recursive: true
|
|
177
|
+
})]);
|
|
178
|
+
await Promise.all([
|
|
179
|
+
cp(new URL("../scaffold/.agents/skills/hitch-applets", import.meta.url), skillTarget, { recursive: true }),
|
|
180
|
+
writeFile(resolve(root, "package.json"), `${JSON.stringify(workspacePackage(config.org), null, 2)}\n`),
|
|
181
|
+
writeFile(resolve(root, "pnpm-workspace.yaml"), workspaceFile(resolvedCli)),
|
|
182
|
+
writeFile(resolve(root, ".node-version"), "24\n"),
|
|
183
|
+
writeGitignore(root),
|
|
184
|
+
writeFile(resolve(root, "AGENTS.md"), agents)
|
|
185
|
+
]);
|
|
186
|
+
await rm(resolve(root, "CLAUDE.md"), { force: true });
|
|
187
|
+
await symlink("AGENTS.md", resolve(root, "CLAUDE.md"));
|
|
188
|
+
}
|
|
189
|
+
function appletPackage(definition, options) {
|
|
190
|
+
return {
|
|
191
|
+
name: definition.code.replaceAll("_", "-"),
|
|
192
|
+
private: true,
|
|
193
|
+
type: "module",
|
|
194
|
+
scripts: {
|
|
195
|
+
dev: "vite",
|
|
196
|
+
build: "applet build",
|
|
197
|
+
generate: "applet generate",
|
|
198
|
+
deploy: "applet deploy",
|
|
199
|
+
test: "vitest run",
|
|
200
|
+
typecheck: "tsc --noEmit"
|
|
201
|
+
},
|
|
202
|
+
dependencies: {
|
|
203
|
+
"@hitch42/applet": "catalog:",
|
|
204
|
+
"@orpc/contract": "catalog:",
|
|
205
|
+
zod: "catalog:",
|
|
206
|
+
...options.hasServer ? { "@orpc/server": "catalog:" } : {},
|
|
207
|
+
...options.hasClient ? {
|
|
208
|
+
"@fontsource-variable/inter": "catalog:",
|
|
209
|
+
"@tanstack/react-query": "catalog:",
|
|
210
|
+
"@tanstack/react-router": "catalog:",
|
|
211
|
+
"@tiptap/core": "catalog:",
|
|
212
|
+
"@tiptap/extension-image": "catalog:",
|
|
213
|
+
"@tiptap/extension-list": "catalog:",
|
|
214
|
+
"@tiptap/extension-table": "catalog:",
|
|
215
|
+
"@tiptap/extensions": "catalog:",
|
|
216
|
+
"@tiptap/markdown": "catalog:",
|
|
217
|
+
"@tiptap/react": "catalog:",
|
|
218
|
+
"@tiptap/starter-kit": "catalog:",
|
|
219
|
+
"class-variance-authority": "catalog:",
|
|
220
|
+
clsx: "catalog:",
|
|
221
|
+
"lucide-react": "catalog:",
|
|
222
|
+
react: "catalog:",
|
|
223
|
+
"react-aria-components": "catalog:",
|
|
224
|
+
"react-dom": "catalog:",
|
|
225
|
+
shadcn: "catalog:",
|
|
226
|
+
streamdown: "catalog:",
|
|
227
|
+
"tailwind-merge": "catalog:",
|
|
228
|
+
"tw-animate-css": "catalog:"
|
|
229
|
+
} : {},
|
|
230
|
+
...definition.dependencies
|
|
231
|
+
},
|
|
232
|
+
devDependencies: {
|
|
233
|
+
"@types/node": "catalog:",
|
|
234
|
+
typescript: "catalog:",
|
|
235
|
+
vite: "catalog:",
|
|
236
|
+
vitest: "catalog:",
|
|
237
|
+
...options.hasClient ? {
|
|
238
|
+
"@tailwindcss/vite": "catalog:",
|
|
239
|
+
"@types/react": "catalog:",
|
|
240
|
+
"@types/react-dom": "catalog:",
|
|
241
|
+
tailwindcss: "catalog:"
|
|
242
|
+
} : {},
|
|
243
|
+
...definition.devDependencies
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
async function writeAppletSupport(root, definition) {
|
|
248
|
+
const hasClient = existsSync(resolve(root, "src/client/routes"));
|
|
249
|
+
const hasAuthorRoot = existsSync(resolve(root, "src/client/routes/__root.tsx"));
|
|
250
|
+
const hasServer = existsSync(resolve(root, "src/server/main.ts"));
|
|
251
|
+
if (!hasClient && !hasServer) throw new Error("An applet must have client routes, a server entry, or both.");
|
|
252
|
+
if (!hasServer && (definition.schedules?.length ?? 0) > 0) throw new Error("An applet with schedules must have src/server/main.ts.");
|
|
253
|
+
await Promise.all(hasClient ? [] : [rm(resolve(root, "index.html"), { force: true }), rm(resolve(root, "components.json"), { force: true })]);
|
|
254
|
+
await Promise.all([
|
|
255
|
+
writeFile(resolve(root, "package.json"), `${JSON.stringify(appletPackage(definition, {
|
|
256
|
+
hasClient,
|
|
257
|
+
hasServer
|
|
258
|
+
}), null, 2)}\n`),
|
|
259
|
+
writeFile(resolve(root, "vite.config.ts"), `import { resolve } from "node:path";
|
|
260
|
+
|
|
261
|
+
import { applet } from "@hitch42/applet/vite";
|
|
262
|
+
${hasClient ? "import tailwindcss from \"@tailwindcss/vite\";\n" : ""}import { defineConfig } from "vite";
|
|
263
|
+
|
|
264
|
+
import manifest from "./applet.manifest.ts";
|
|
265
|
+
|
|
266
|
+
export default defineConfig({
|
|
267
|
+
plugins: [applet(manifest)${hasClient ? ", tailwindcss()" : ""}],
|
|
268
|
+
resolve: {
|
|
269
|
+
alias: { "@": resolve(import.meta.dirname, "./src") },
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
`),
|
|
273
|
+
hasClient ? writeFile(resolve(root, "tsr.config.json"), `${JSON.stringify({
|
|
274
|
+
routesDirectory: "./src/client/routes",
|
|
275
|
+
generatedRouteTree: "./.hitch/routeTree.gen.ts",
|
|
276
|
+
routeFileIgnorePattern: "__root",
|
|
277
|
+
virtualRouteConfig: {
|
|
278
|
+
type: "root",
|
|
279
|
+
file: hasAuthorRoot ? "__root.tsx" : relative(resolve(root, "src/client/routes"), resolve(root, ".hitch/appletRoot.gen.tsx")),
|
|
280
|
+
children: [{
|
|
281
|
+
type: "physical",
|
|
282
|
+
directory: ".",
|
|
283
|
+
pathPrefix: definition.code
|
|
284
|
+
}]
|
|
285
|
+
}
|
|
286
|
+
}, null, 2)}\n`) : rm(resolve(root, "tsr.config.json"), { force: true }),
|
|
287
|
+
hasClient ? writeFile(resolve(root, "index.html"), `<!doctype html>
|
|
288
|
+
<html lang="en">
|
|
289
|
+
<head>
|
|
290
|
+
<meta charset="UTF-8" />
|
|
291
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
292
|
+
<link rel="stylesheet" href="/theme.css" vite-ignore />
|
|
293
|
+
<title>${definition.name}</title>
|
|
294
|
+
</head>
|
|
295
|
+
<body>
|
|
296
|
+
<div id="root"></div>
|
|
297
|
+
<script type="module" src="/.hitch/entry.gen.tsx"><\/script>
|
|
298
|
+
</body>
|
|
299
|
+
</html>
|
|
300
|
+
`) : void 0,
|
|
301
|
+
writeFile(resolve(root, "tsconfig.json"), `{
|
|
302
|
+
"compilerOptions": {
|
|
303
|
+
"noEmit": true,
|
|
304
|
+
"strict": true,
|
|
305
|
+
"noUncheckedIndexedAccess": true,
|
|
306
|
+
"skipLibCheck": true,
|
|
307
|
+
"target": "ES2022",
|
|
308
|
+
"module": "preserve",
|
|
309
|
+
"moduleDetection": "force",
|
|
310
|
+
"isolatedModules": true,
|
|
311
|
+
"verbatimModuleSyntax": true,
|
|
312
|
+
"resolveJsonModule": true,
|
|
313
|
+
"allowImportingTsExtensions": true,
|
|
314
|
+
"jsx": "react-jsx",
|
|
315
|
+
"types": ["vite/client", "node"],
|
|
316
|
+
"paths": {
|
|
317
|
+
"@/*": ["./src/*"]
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
"include": [
|
|
321
|
+
"./vite.config.ts",
|
|
322
|
+
"./applet.manifest.ts",
|
|
323
|
+
"./.hitch/*.ts",
|
|
324
|
+
"./.hitch/*.tsx",
|
|
325
|
+
"./src/**/*.ts",
|
|
326
|
+
"./src/**/*.tsx"
|
|
327
|
+
]
|
|
328
|
+
}
|
|
329
|
+
`),
|
|
330
|
+
hasClient ? writeFile(resolve(root, "components.json"), await readFile(new URL("../scaffold/applet/components.json", import.meta.url), "utf8")) : void 0
|
|
331
|
+
]);
|
|
332
|
+
}
|
|
333
|
+
async function prepareAppletProject(appletRoot = process.cwd()) {
|
|
334
|
+
const definition = await loadAppletSourceDefinition(appletRoot);
|
|
335
|
+
const workspaceRoot = dirname(dirname(resolve(appletRoot)));
|
|
336
|
+
const workspace = readWorkspaceConfig(workspaceRoot);
|
|
337
|
+
if (workspace === void 0) throw new Error(`No Hitch workspace found for ${appletRoot}.`);
|
|
338
|
+
const cli = await loadCliManifest({ allowDevelopment: true });
|
|
339
|
+
await Promise.all([cli === void 0 ? void 0 : writeWorkspaceSupport(workspaceRoot, workspace, cli), writeAppletSupport(appletRoot, definition)]);
|
|
340
|
+
return {
|
|
341
|
+
definition,
|
|
342
|
+
workspaceRoot
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
//#endregion
|
|
346
|
+
export { writeWorkspaceSupport as a, writeAppletSupport as i, loadCliManifest as n, prepareAppletProject as r, assertWorkspaceCli as t };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { a as UploadedHitchFile } from "./upload-YbRKEqR6.js";
|
|
2
|
+
import { ReactElement } from "react";
|
|
3
|
+
import "@tiptap/core";
|
|
4
|
+
//#region src/react/markdown.d.ts
|
|
5
|
+
type MarkdownFiles = {
|
|
6
|
+
download: (id: string) => Promise<Pick<UploadedHitchFile, "downloadUrl">>;
|
|
7
|
+
};
|
|
8
|
+
type MarkdownProps = {
|
|
9
|
+
children: string;
|
|
10
|
+
files?: MarkdownFiles;
|
|
11
|
+
className?: string;
|
|
12
|
+
};
|
|
13
|
+
declare function Markdown({ children, files, className }: MarkdownProps): ReactElement;
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/react/markdown-editor-extensions.d.ts
|
|
16
|
+
type MarkdownEditorFiles = {
|
|
17
|
+
upload: (file: File) => Promise<Pick<UploadedHitchFile, "id" | "downloadUrl">>;
|
|
18
|
+
download: (id: string) => Promise<Pick<UploadedHitchFile, "downloadUrl">>;
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/react/markdown-editor.d.ts
|
|
22
|
+
type MarkdownEditorProps = {
|
|
23
|
+
value: string;
|
|
24
|
+
onChange: (value: string) => void;
|
|
25
|
+
files?: MarkdownEditorFiles;
|
|
26
|
+
id?: string;
|
|
27
|
+
placeholder?: string;
|
|
28
|
+
required?: boolean;
|
|
29
|
+
minLength?: number;
|
|
30
|
+
maxLength?: number;
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
className?: string;
|
|
33
|
+
onUploadError?: (error: unknown, file: File) => void;
|
|
34
|
+
};
|
|
35
|
+
declare function MarkdownEditor({ value, onChange, files, id, placeholder, required, minLength, maxLength, disabled, className, onUploadError }: MarkdownEditorProps): ReactElement;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { Markdown, MarkdownEditor, type MarkdownEditorFiles, type MarkdownEditorProps, type MarkdownFiles, type MarkdownProps };
|