apps 0.8.3 → 1.0.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.
Files changed (50) hide show
  1. package/README.md +51 -166
  2. package/dist/authoring/index.d.ts +205 -0
  3. package/dist/authoring/index.js +26 -0
  4. package/dist/chunk-B7NEUIJL.js +14 -0
  5. package/dist/chunk-J5AVXTHA.js +154 -0
  6. package/dist/chunk-LWKQDDLZ.js +98 -0
  7. package/dist/chunk-NFLNA2SC.js +11 -0
  8. package/dist/chunk-RZA7AXBK.js +107 -0
  9. package/dist/index.d.ts +9 -0
  10. package/dist/index.js +26 -0
  11. package/dist/manifest/collect.d.ts +64 -0
  12. package/dist/manifest/contract.d.ts +55 -0
  13. package/dist/manifest/contract.js +8 -0
  14. package/dist/manifest/index.d.ts +18 -0
  15. package/dist/manifest/index.js +16 -0
  16. package/dist/runner/index.d.ts +4 -0
  17. package/dist/runner/index.js +81 -0
  18. package/dist/runner/protocol.d.ts +28 -0
  19. package/dist/runner/protocol.js +6 -0
  20. package/dist/workerd/index.d.ts +2 -0
  21. package/dist/workerd/index.js +6 -0
  22. package/package.json +51 -37
  23. package/src/authoring/index.ts +340 -0
  24. package/src/index.ts +9 -0
  25. package/src/manifest/collect.ts +198 -0
  26. package/src/manifest/contract.ts +55 -0
  27. package/src/manifest/index.ts +224 -0
  28. package/src/runner/index.ts +117 -0
  29. package/src/runner/protocol.ts +41 -0
  30. package/src/workerd/index.ts +2 -0
  31. package/.npmignore +0 -1
  32. package/bin/apps +0 -36
  33. package/bin/build +0 -14
  34. package/bin/doc +0 -16
  35. package/dist/app.js +0 -9773
  36. package/dist/app.min.js +0 -5
  37. package/dist/schema.html +0 -119
  38. package/dist/schema.md +0 -156
  39. package/index.html +0 -6
  40. package/index.js +0 -115
  41. package/lib/addons.js +0 -67
  42. package/schema.js +0 -100
  43. package/templates/app.mustache.html +0 -36
  44. package/templates/build.mustache.html +0 -13
  45. package/templates/schema.mustache.html +0 -20
  46. package/test/addons.js +0 -172
  47. package/test/cli.js +0 -53
  48. package/test/fixtures/app.json +0 -32
  49. package/test/index.js +0 -277
  50. package/test/mocha.opts +0 -2
package/README.md CHANGED
@@ -1,168 +1,53 @@
1
1
  # apps
2
2
 
3
- [apps](https://www.npmjs.org/package/apps) is a node module for creating and validating Heroku app.json files. It works in node.js and the browser, and also has a CLI.
4
-
5
- ## Schema
6
-
7
- The app.json schema is defined using the [JSON Schema](http://json-schema.org/)
8
- specification and is validated with the awesome
9
- [revalidator](https://github.com/flatiron/revalidator#readme) node module.
10
-
11
- See the schema at [schema.js](/schema.js).
12
-
13
- ## Installation and Usage
14
-
15
- ### Programmatic usage with Node.js or Browserify
16
-
17
- Download the module from npm and save it to your package.json:
18
-
19
- ```sh
20
- npm install apps --save
21
- ```
22
-
23
- Require it in your script:
24
-
25
- ```js
26
- var App = require("apps")
27
- ```
28
-
29
- ### Usage in the Browser (without Browserify)
30
-
31
- If browserify isn't your thing, use the pre-compiled browser-ready bundle in
32
- [dist/app.js](/dist/app.js). Include this file in your html page and it will create
33
- `window.App` for you.
34
-
35
- ### Usage on the Command Line
36
-
37
- To use this module on the command line, you'll need to install it globally using npm:
38
-
39
- ```sh
40
- npm install apps --global
41
- ```
42
-
43
- Now you can run `app` or `apps` from any directory:
44
-
45
- ```sh
46
- $ app
47
-
48
- Usage: app [options] [command]
49
-
50
- Commands:
51
-
52
- schema [options] Write the app.json schema to STDOUT
53
-
54
- Options:
55
-
56
- -h, --help output usage information
57
- -V, --version output the version number
58
-
59
- Use 'app <command> --help' to get more information about a specific command.
60
- ```
61
-
62
- ## Class Methods
63
-
64
- ### App.new(payload)
65
-
66
- Instantiate with a JSON filename:
67
-
68
- ```js
69
- var app = App.new(__dirname + "/path/to/app.json")
70
- ```
71
-
72
- Instantiate with a JSON string:
73
-
74
- ```js
75
- var json = "{name: \"small-sharp-tool\", description: \"This app does one little thing, and does it well.\"}"
76
- var app = App.new(json)
77
- ```
78
-
79
- Instantiate with a JavaScript object:
80
-
81
- ```js
82
- var app = App.new({
83
- name: "small-sharp-tool",
84
- description: "This app does one little thing, and does it well."
85
- })
86
- ```
87
-
88
- ### App.fetch(url, callback)
89
-
90
- You can fetch app manifests straight from GitHub. The
91
- [github-raw-cors-proxy](https://github.com/zeke/github-raw-cors-proxy) service is used
92
- to make the `app.json` file downloadable from browsers.
93
-
94
- `url` can be a fully qualified GitHub URL, or a shorthand `user/repo` string:
95
-
96
- ```js
97
- App.fetch('zeke/harp-slideshow-template', function(err, manifest) {
98
- console.log(err, manifest)
99
- })
100
- ```
101
-
102
- ### App.example
103
-
104
- Generates an example manifest from example content in the [schema](/schema.js).
105
-
106
- ```js
107
- App.example
108
- ```
109
-
110
- ## Instance Methods
111
-
112
- ### app.valid
113
-
114
- A getter method that validates the app manifest and returns `true` or `false`
115
-
116
- ### app.errors
117
-
118
- Returns `null` if app manifest is valid.
119
-
120
- Returns an array of error objects if invalid:
121
-
122
- ```js
123
- [
124
- {property: "name", message: "is required"},
125
- {property: "website", message: "is not a valid url"}
126
- ]
127
- ```
128
-
129
- ### app.toJSON()
130
-
131
- Return a pretty JSON string representation of the manifest, without any superfluous properties.
132
-
133
- ### app.getAddonPrices(callback)
134
-
135
- Fetch pricing data about the app's required addons by hitting the Heroku Platform API.
136
-
137
- ## Tests
138
-
139
- ```
140
- npm install
141
- npm test
142
- ```
143
-
144
- ## Bundle
145
-
146
- To prepare a browser-ready bundle, run the following:
147
-
148
- ```sh
149
- npm run build
150
-
151
- # Wrote dist/app.js
152
- # Wrote dist/app.min.js
153
- ```
154
-
155
- ## Docs
156
-
157
- Genarate human-friendly docs from the schema
158
-
159
- ```sh
160
- npm run docs
161
-
162
- # Wrote dist/schema.md
163
- # Wrote dist/schema.html
164
- ```
165
-
166
- ## License
167
-
168
- MIT
3
+ Author executor apps in plain TypeScript: declare the services a tool talks
4
+ to, the auth they need, and the tools themselves. The executor platform loads,
5
+ validates, and serves them; credentials never enter your handler — each
6
+ requirement arrives as a capability that spends them for you.
7
+
8
+ > `1.0.0-beta.x` is a new package under this name, unrelated to `0.x`.
9
+
10
+ ```ts
11
+ // app.ts
12
+ import { defineApp, secrets, service } from "apps";
13
+
14
+ export const github = service({
15
+ origin: "api.github.com",
16
+ label: "GitHub",
17
+ auth: {
18
+ pat: secrets({
19
+ label: "Personal access token",
20
+ help: "Create a fine-grained token at github.com/settings/tokens.",
21
+ fields: { token: { label: "Token", secret: true } },
22
+ }),
23
+ },
24
+ });
25
+
26
+ export default defineApp({
27
+ key: "github",
28
+ label: "GitHub",
29
+ description: "Repositories, issues, and pull requests.",
30
+ });
31
+ ```
32
+
33
+ ```ts
34
+ // tools/get-repo.ts
35
+ import { defineTool } from "apps";
36
+ import { z } from "zod";
37
+ import { github } from "../app";
38
+
39
+ export default defineTool({
40
+ description: "Fetch a repository",
41
+ input: z.object({ owner: z.string(), repo: z.string() }),
42
+ requirements: { gh: github },
43
+ handler: async ({ owner, repo }, ctx) => {
44
+ const response = await ctx.gh.fetch(`/repos/${owner}/${repo}`);
45
+ return response.json();
46
+ },
47
+ });
48
+ ```
49
+
50
+ The root export is pure and synchronous — no effect dependency, no node
51
+ builtins — so authored files run unmodified anywhere, including sandboxed
52
+ workers. Host machinery lives behind subpaths (`apps/manifest`,
53
+ `apps/runner`) and requires `effect`.
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Synchronous authoring primitives imported by app.ts and tools/*.ts.
3
+ * Authored modules record plain claims without I/O or user context; the
4
+ * manifest loader owns execution, validation, and effects.
5
+ */
6
+ export interface SecretsMethod<F extends Readonly<Record<string, SecretField>> = Readonly<Record<string, SecretField>>> {
7
+ readonly kind: "secrets";
8
+ readonly label: string;
9
+ /** Markdown shown under this auth method on the connect page, for example:
10
+ * "Create a fine-grained token at github.com/settings/tokens." */
11
+ readonly help?: string;
12
+ readonly fields: F;
13
+ }
14
+ export interface SecretField {
15
+ readonly label: string;
16
+ /** Form masking. Stated, never guessed from the field name. */
17
+ readonly secret: boolean;
18
+ }
19
+ /**
20
+ * An oauth auth method is a secrets-shaped auth method with acquisition strategies
21
+ * layered on: the material contract is always {token, refresh_token?,
22
+ * expires_at?}, and the host fills it by the best strategy it can offer
23
+ * — authorization-code ceremony, device-code ceremony, or paste. Paste
24
+ * is the always-on floor (a pasted token spends identically), so the
25
+ * auth method works on any deployment; ceremony is progressive enhancement.
26
+ * Handlers cannot tell how material arrived.
27
+ */
28
+ export interface OAuthMethod {
29
+ readonly kind: "oauth";
30
+ readonly label: string;
31
+ /** Markdown shown under this auth method on the connect page, for example:
32
+ * "Create a fine-grained token at github.com/settings/tokens." */
33
+ readonly help?: string;
34
+ /** Endpoints only when the service publishes no well-knowns —
35
+ * discovery (RFC 9728 + AS metadata) outranks these when present.
36
+ * Authors state facts about the service, never about the client. */
37
+ readonly authorizationEndpoint?: string;
38
+ readonly tokenEndpoint?: string;
39
+ readonly scopes?: readonly string[];
40
+ /** Set false only when a user-pasted token genuinely cannot work
41
+ * (audience-bound tokens, DPoP). Stated, never inferred. */
42
+ readonly paste?: false;
43
+ }
44
+ export type AuthMethod = SecretsMethod | OAuthMethod;
45
+ export declare const secrets: <const F extends Readonly<Record<string, SecretField>>>(opts: {
46
+ readonly label: string;
47
+ /** Markdown shown under this auth method on the connect page, for example:
48
+ * "Create a fine-grained token at github.com/settings/tokens." */
49
+ readonly help?: string;
50
+ readonly fields: F;
51
+ }) => SecretsMethod<F>;
52
+ export declare const oauth: (opts: {
53
+ readonly label: string;
54
+ /** Markdown shown under this auth method on the connect page, for example:
55
+ * "Create a fine-grained token at github.com/settings/tokens." */
56
+ readonly help?: string;
57
+ readonly authorizationEndpoint?: string;
58
+ readonly tokenEndpoint?: string;
59
+ readonly scopes?: readonly string[];
60
+ readonly paste?: false;
61
+ }) => OAuthMethod;
62
+ export interface ServiceDecl {
63
+ /** Canonical origin, possibly templated: "https://{region}.posthog.com". */
64
+ readonly origin: string;
65
+ /** Hosts beyond the origin the credential legitimately travels to. */
66
+ readonly reach?: readonly string[];
67
+ /** Connection-scoped non-secret connect-form values; keys may fill
68
+ * origin templates. */
69
+ readonly params?: Readonly<Record<string, ParamSpec>>;
70
+ /** Named auth methods. Key = the grant name tools switch on. */
71
+ readonly auth?: Readonly<Record<string, AuthMethod>>;
72
+ }
73
+ export interface ParamSpec {
74
+ readonly label: string;
75
+ readonly options?: readonly string[];
76
+ }
77
+ /** What the manifest pipeline reads off a requirement value. */
78
+ export interface RequirementSpec {
79
+ readonly service: ServiceDecl;
80
+ readonly arity: "one" | "optional" | "many";
81
+ readonly grant?: string;
82
+ readonly description?: string;
83
+ }
84
+ declare const RequirementKey: unique symbol;
85
+ /**
86
+ * A requirement value in a tool's `requirements` record. A Service IS
87
+ * a requirement (bare value = arity "one"); .optional()/.array()
88
+ * return terminal Requirements so nonsense chains do not typecheck.
89
+ */
90
+ export interface Requirement<D extends ServiceDecl = ServiceDecl> {
91
+ readonly [RequirementKey]: RequirementSpec;
92
+ describe(text: string): Requirement<D>;
93
+ }
94
+ export interface Service<D extends ServiceDecl = ServiceDecl> extends Requirement<D> {
95
+ readonly decl: D;
96
+ optional(): Requirement<D>;
97
+ array(): Requirement<D>;
98
+ /** Pin one auth method: this tool supports only the named grant. */
99
+ grant(name: keyof NonNullable<D["auth"]> & string): Service<D>;
100
+ describe(text: string): Service<D>;
101
+ }
102
+ /** A publish-time authoring problem accumulated without interrupting defines. */
103
+ export interface Problem {
104
+ readonly message: string;
105
+ }
106
+ /**
107
+ * A dynamic tool-set claim: tools discovered from a live surface (an
108
+ * MCP server's tool list, an OpenAPI document) rather than authored one
109
+ * file at a time. The claim is recorded at publish time; discovery runs
110
+ * later, per connection — an app with zero static tools and one claim
111
+ * saves green, and listing never blocks on an upstream.
112
+ */
113
+ export type DynamicClaimSpec = {
114
+ readonly kind: "mcp";
115
+ readonly requirement: RequirementSpec;
116
+ /** MCP endpoint URL. Absent = the service origin at its root path. */
117
+ readonly endpoint?: string;
118
+ } | {
119
+ readonly kind: "openapi";
120
+ readonly requirement: RequirementSpec;
121
+ /** OpenAPI document: an absolute URL or an app-relative path. */
122
+ readonly spec: string;
123
+ };
124
+ declare const DynamicKey: unique symbol;
125
+ /** The value fromMCP/fromOpenAPI return: an opaque claim handle a tool
126
+ * file default-exports. The spec is undefined when the claim was
127
+ * invalid (a problem was recorded instead). */
128
+ export interface DynamicToolSet {
129
+ readonly [DynamicKey]: DynamicClaimSpec | undefined;
130
+ }
131
+ /** A registered dynamic claim and its source attribution. @internal */
132
+ export interface RegisteredDynamic {
133
+ readonly spec: DynamicClaimSpec;
134
+ readonly file: string;
135
+ }
136
+ /** A registered service declaration and its source attribution. @internal */
137
+ export interface RegisteredService {
138
+ readonly decl: ServiceDecl;
139
+ readonly file: string;
140
+ }
141
+ /** A registered tool declaration and its source attribution. @internal */
142
+ export interface RegisteredTool {
143
+ readonly spec: ToolSpec;
144
+ readonly file: string;
145
+ }
146
+ /** Mutable claim registry populated while authored modules execute. @internal */
147
+ export interface Registry {
148
+ app: AppManifest | undefined;
149
+ readonly services: Map<string, RegisteredService>;
150
+ readonly tools: Map<string, RegisteredTool>;
151
+ readonly dynamic: Map<string, RegisteredDynamic>;
152
+ readonly problems: Problem[];
153
+ readonly toolFiles: Map<string, {
154
+ definitions: number;
155
+ hasUnnamedTool: boolean;
156
+ }>;
157
+ }
158
+ /** Creates an isolated empty authoring registry. @internal */
159
+ export declare const newRegistry: () => Registry;
160
+ /** Swaps the registry receiving subsequent authoring claims. @internal */
161
+ export declare const setActiveRegistry: (registry: Registry) => Registry;
162
+ /** Sets source-file attribution for subsequent authoring claims. @internal */
163
+ export declare const __enterFile: (path: string) => void;
164
+ /** Clears source-file attribution after an authored module import. @internal */
165
+ export declare const __leaveFile: () => void;
166
+ export declare const service: <const D extends ServiceDecl>(decl: D) => Service<D>;
167
+ export declare const requirementSpecOf: (value: unknown) => RequirementSpec | undefined;
168
+ export interface AppManifest {
169
+ readonly kind: "executor-app";
170
+ readonly key: string;
171
+ readonly label: string;
172
+ readonly description: string;
173
+ }
174
+ export declare const defineApp: (manifest: {
175
+ readonly key: string;
176
+ readonly label: string;
177
+ readonly description: string;
178
+ }) => AppManifest;
179
+ /** Any Standard Schema (zod preferred) or a raw JSON-schema object. */
180
+ export type SchemaLike = object;
181
+ export interface ToolAnnotations {
182
+ readonly readOnly?: boolean;
183
+ readonly destructive?: boolean;
184
+ /** Facts, not flow: shortfall surfaces as the scope challenge. */
185
+ readonly scopes?: readonly string[];
186
+ }
187
+ export interface ToolSpec {
188
+ /** Address segment. Single-tool files may omit it (filename). */
189
+ readonly name?: string;
190
+ readonly description: string;
191
+ readonly requirements?: Readonly<Record<string, Requirement>>;
192
+ readonly input: SchemaLike;
193
+ readonly output?: SchemaLike;
194
+ readonly annotations?: ToolAnnotations;
195
+ handler(input: never, ctx: never): Promise<unknown>;
196
+ }
197
+ declare const ToolKey: unique symbol;
198
+ export interface Tool {
199
+ readonly [ToolKey]: ToolSpec;
200
+ }
201
+ export declare const defineTool: (spec: ToolSpec) => Tool;
202
+ export declare const toolSpecOf: (value: unknown) => ToolSpec | undefined;
203
+ /** A tool file's default export. */
204
+ export type ToolExport = Tool | ReadonlyArray<Tool>;
205
+ export {};
@@ -0,0 +1,26 @@
1
+ import {
2
+ __enterFile,
3
+ __leaveFile,
4
+ defineApp,
5
+ defineTool,
6
+ newRegistry,
7
+ oauth,
8
+ requirementSpecOf,
9
+ secrets,
10
+ service,
11
+ setActiveRegistry,
12
+ toolSpecOf
13
+ } from "../chunk-LWKQDDLZ.js";
14
+ export {
15
+ __enterFile,
16
+ __leaveFile,
17
+ defineApp,
18
+ defineTool,
19
+ newRegistry,
20
+ oauth,
21
+ requirementSpecOf,
22
+ secrets,
23
+ service,
24
+ setActiveRegistry,
25
+ toolSpecOf
26
+ };
@@ -0,0 +1,14 @@
1
+ // src/manifest/contract.ts
2
+ import { Data, Schema } from "effect";
3
+ var JsonSchemaObject = Schema.Record(Schema.String, Schema.Json);
4
+ var PublishError = class extends Data.TaggedError("PublishError") {
5
+ get message() {
6
+ return `publish failed:
7
+ ${this.problems.map((problem) => ` - ${problem}`).join("\n")}`;
8
+ }
9
+ };
10
+
11
+ export {
12
+ JsonSchemaObject,
13
+ PublishError
14
+ };
@@ -0,0 +1,154 @@
1
+ import {
2
+ __enterFile,
3
+ __leaveFile,
4
+ newRegistry,
5
+ setActiveRegistry
6
+ } from "./chunk-LWKQDDLZ.js";
7
+ import {
8
+ JsonSchemaObject,
9
+ PublishError
10
+ } from "./chunk-B7NEUIJL.js";
11
+ import {
12
+ collectRegistry
13
+ } from "./chunk-RZA7AXBK.js";
14
+
15
+ // src/manifest/index.ts
16
+ import { fileURLToPath, pathToFileURL } from "node:url";
17
+ import { Crypto, Effect, FileSystem, Schema, Semaphore } from "effect";
18
+ var hasStandardJsonSchema = (input) => "~standard" in input && typeof input["~standard"].jsonSchema?.input === "function";
19
+ var compileInputSchema = (input, where) => {
20
+ if ("~standard" in input) {
21
+ if (!hasStandardJsonSchema(input))
22
+ return {
23
+ problem: `${where}: input is a Standard Schema without the jsonSchema extension \u2014 use a library that implements it (zod 4+, effect Schema) or pass raw JSON schema`
24
+ };
25
+ }
26
+ try {
27
+ const compiled = strictify(
28
+ hasStandardJsonSchema(input) ? input["~standard"].jsonSchema.input({ target: "draft-2020-12" }) : structuredClone(input)
29
+ );
30
+ return { value: Schema.decodeUnknownSync(JsonSchemaObject)(compiled) };
31
+ } catch (error) {
32
+ return { problem: schemaPublishProblem(where, error) };
33
+ }
34
+ };
35
+ var schemaPublishProblem = (where, error) => `${where}: input schema could not compile to JSON schema (${error instanceof Error ? error.message : String(error)})`;
36
+ var strictify = (node) => {
37
+ if (typeof node !== "object" || node === null) return node;
38
+ if (Array.isArray(node)) return node.map(strictify);
39
+ const object = { ...node };
40
+ if (object.type === "object" && object.additionalProperties === void 0)
41
+ object.additionalProperties = false;
42
+ for (const key of ["items", "anyOf", "oneOf", "allOf"])
43
+ if (key in object) object[key] = strictify(object[key]);
44
+ for (const key of ["properties", "$defs"])
45
+ if (object[key] !== void 0 && typeof object[key] === "object" && object[key] !== null)
46
+ object[key] = Object.fromEntries(
47
+ Object.entries(object[key]).map(([name, value]) => [
48
+ name,
49
+ strictify(value)
50
+ ])
51
+ );
52
+ return object;
53
+ };
54
+ var digestOf = (content) => Effect.gen(function* () {
55
+ const crypto = yield* Crypto.Crypto;
56
+ const bytes = yield* Effect.orDie(
57
+ crypto.digest("SHA-256", new TextEncoder().encode(content))
58
+ );
59
+ return Array.from(bytes.slice(0, 8), (byte) => byte.toString(16).padStart(2, "0")).join("");
60
+ });
61
+ var loadPermit = Semaphore.makeUnsafe(1);
62
+ var runNonce = 0;
63
+ var baseAppImports = /* @__PURE__ */ new Set();
64
+ var moduleFailure = (where, error) => new PublishError({
65
+ problems: [
66
+ `${where}: failed to execute (${error instanceof Error ? error.message : String(error)})`
67
+ ]
68
+ });
69
+ var canonicalModuleUrl = (path, where) => Effect.gen(function* () {
70
+ const fs = yield* FileSystem.FileSystem;
71
+ const realPath = yield* fs.realPath(path).pipe(
72
+ Effect.mapError((error) => moduleFailure(where, error))
73
+ );
74
+ return pathToFileURL(realPath);
75
+ });
76
+ var importModule = (specifier, where) => Effect.tryPromise({
77
+ try: () => import(specifier).then(() => void 0),
78
+ catch: (error) => moduleFailure(where, error)
79
+ });
80
+ var recordModuleProblems = (registry, effect) => effect.pipe(
81
+ Effect.catchTag(
82
+ "PublishError",
83
+ (error) => Effect.sync(() => {
84
+ registry.problems.push(...error.problems.map((message) => ({ message })));
85
+ })
86
+ )
87
+ );
88
+ var executeModules = (appDir, files) => loadPermit.withPermit(
89
+ Effect.uninterruptibleMask(
90
+ (restore) => Effect.gen(function* () {
91
+ const registry = newRegistry();
92
+ const previous = yield* Effect.sync(() => setActiveRegistry(registry));
93
+ const nonce = yield* Effect.sync(() => ++runNonce);
94
+ const execute = Effect.gen(function* () {
95
+ __enterFile("app.ts");
96
+ yield* recordModuleProblems(
97
+ registry,
98
+ Effect.gen(function* () {
99
+ const appUrl = yield* canonicalModuleUrl(`${appDir}/app.ts`, "app.ts");
100
+ const baseUrl = appUrl.href;
101
+ const specifier = baseAppImports.has(baseUrl) ? `${fileURLToPath(appUrl)}?m=${nonce}` : baseUrl;
102
+ yield* importModule(specifier, "app.ts");
103
+ yield* Effect.sync(() => baseAppImports.add(baseUrl));
104
+ })
105
+ );
106
+ for (const file of files) {
107
+ const where = `tools/${file}`;
108
+ __enterFile(where);
109
+ yield* recordModuleProblems(
110
+ registry,
111
+ Effect.gen(function* () {
112
+ const url = yield* canonicalModuleUrl(`${appDir}/tools/${file}`, where);
113
+ yield* importModule(`${fileURLToPath(url)}?m=${nonce}`, where);
114
+ })
115
+ );
116
+ }
117
+ });
118
+ yield* restore(execute).pipe(
119
+ Effect.ensuring(
120
+ Effect.sync(() => {
121
+ __leaveFile();
122
+ setActiveRegistry(previous);
123
+ })
124
+ )
125
+ );
126
+ return registry;
127
+ })
128
+ )
129
+ );
130
+ var loadApp = (appDir) => Effect.gen(function* () {
131
+ const fs = yield* FileSystem.FileSystem;
132
+ const files = (yield* fs.readDirectory(`${appDir}/tools`).pipe(
133
+ Effect.mapError((error) => new PublishError({ problems: [`tools/: ${error.message}`] }))
134
+ )).filter((file) => file.endsWith(".ts")).sort();
135
+ const registry = yield* executeModules(appDir, files);
136
+ const collected = collectRegistry(registry, compileInputSchema);
137
+ if (collected.problems.length > 0)
138
+ return yield* new PublishError({ problems: collected.problems });
139
+ if (!("manifest" in collected))
140
+ return yield* Effect.die("validated registry projection unexpectedly missing");
141
+ return {
142
+ registry,
143
+ result: {
144
+ digest: `snap:${yield* digestOf(collected.canonical)}`,
145
+ ...collected.manifest
146
+ }
147
+ };
148
+ });
149
+ var manifest = (appDir) => Effect.map(loadApp(appDir), ({ result }) => result);
150
+
151
+ export {
152
+ loadApp,
153
+ manifest
154
+ };