@pylonsync/sdk 0.11.5 → 0.12.0
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/index.d.ts +52 -0
- package/package.json +1 -1
- package/src/build-config.test.ts +47 -0
- package/src/index.ts +104 -1
package/dist/index.d.ts
CHANGED
|
@@ -684,6 +684,56 @@ export interface AppManifest {
|
|
|
684
684
|
* `pylon deploy` refuses to ship when one is missing from the
|
|
685
685
|
* project's secrets. */
|
|
686
686
|
requiredEnv?: ManifestRequiredEnv[];
|
|
687
|
+
/** Production build settings (`pylon build`). */
|
|
688
|
+
build?: BuildConfig;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Production build settings for `pylon build`. Dev (`pylon dev`) ignores the
|
|
692
|
+
* browser settings; they apply to the artifact and to `pylon start` builds.
|
|
693
|
+
*
|
|
694
|
+
* ```ts
|
|
695
|
+
* buildManifest({
|
|
696
|
+
* // ...
|
|
697
|
+
* build: {
|
|
698
|
+
* target: ["chrome >= 90", "safari >= 14", "firefox >= 90"],
|
|
699
|
+
* polyfill: "usage",
|
|
700
|
+
* server: { external: ["sharp"] },
|
|
701
|
+
* },
|
|
702
|
+
* });
|
|
703
|
+
* ```
|
|
704
|
+
*/
|
|
705
|
+
export interface BuildConfig {
|
|
706
|
+
/** Browser target for client JavaScript: an ECMAScript edition
|
|
707
|
+
* (`"es2018"` to `"es2022"`) or browserslist queries. Unset: modern
|
|
708
|
+
* output, no lowering. Needs `@swc/core` and `browserslist` in the app's
|
|
709
|
+
* devDependencies. The client runtime needs ES modules and dynamic
|
|
710
|
+
* `import()`, so browsers older than Chrome 63, Firefox 67, and Safari
|
|
711
|
+
* 11.1 cannot run it at any target. */
|
|
712
|
+
target?: string | string[];
|
|
713
|
+
/** core-js polyfills for `target`. `"usage"` adds the features the bundle
|
|
714
|
+
* uses; `"entry"` adds every feature the target lacks. Needs `core-js` in
|
|
715
|
+
* the app's dependencies. Default `false`. */
|
|
716
|
+
polyfill?: false | "usage" | "entry";
|
|
717
|
+
css?: {
|
|
718
|
+
/** CSS target (prefixes, nesting, color syntax), same forms as
|
|
719
|
+
* `target`. Default: `target`. Needs `lightningcss` and `browserslist`. */
|
|
720
|
+
target?: string | string[];
|
|
721
|
+
};
|
|
722
|
+
/** Emit source maps for the client and server bundles. Default `false`. */
|
|
723
|
+
sourcemap?: boolean;
|
|
724
|
+
server?: {
|
|
725
|
+
/** Bundle npm packages into the server output. `false` ships every
|
|
726
|
+
* dependency in `server/node_modules`. Default `true`. */
|
|
727
|
+
bundle?: boolean;
|
|
728
|
+
/** Packages to leave out of the server bundle and ship in
|
|
729
|
+
* `server/node_modules` with their dependencies: native modules and
|
|
730
|
+
* packages that read their own files at run time. */
|
|
731
|
+
external?: string[];
|
|
732
|
+
};
|
|
733
|
+
/** Project files or directories to copy into the artifact, for files the
|
|
734
|
+
* app reads from disk at run time (content/, data/). `public/` and image
|
|
735
|
+
* files under `app/` are always copied. */
|
|
736
|
+
include?: string[];
|
|
687
737
|
}
|
|
688
738
|
/** One environment variable the app declares it needs. */
|
|
689
739
|
export interface ManifestRequiredEnv {
|
|
@@ -1121,6 +1171,8 @@ export declare function buildManifest(options: {
|
|
|
1121
1171
|
* ```
|
|
1122
1172
|
*/
|
|
1123
1173
|
requiredEnv?: ManifestRequiredEnv[];
|
|
1174
|
+
/** Production build settings for `pylon build`. See `BuildConfig`. */
|
|
1175
|
+
build?: BuildConfig;
|
|
1124
1176
|
/** Set by `discoverFunctions()` (spread its result into this call).
|
|
1125
1177
|
* When true, the framework's AgentRun/AgentMessage entities and
|
|
1126
1178
|
* their owner-scoping policies are appended to the manifest —
|
package/package.json
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `buildManifest({ build })`: the production build block reaches the
|
|
3
|
+
* manifest as written, and a value `pylon build` cannot use fails when app.ts
|
|
4
|
+
* runs, not at build time.
|
|
5
|
+
*/
|
|
6
|
+
import { expect, test } from "bun:test";
|
|
7
|
+
import { buildManifest, entity, field } from "./index";
|
|
8
|
+
|
|
9
|
+
const base = {
|
|
10
|
+
name: "t",
|
|
11
|
+
version: "0",
|
|
12
|
+
entities: [entity("Doc", { title: field.string() })],
|
|
13
|
+
routes: [],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
test("the build block is copied into the manifest", () => {
|
|
17
|
+
const build = {
|
|
18
|
+
target: ["chrome >= 90", "safari >= 14"],
|
|
19
|
+
polyfill: "usage" as const,
|
|
20
|
+
css: { target: "es2019" },
|
|
21
|
+
server: { external: ["sharp"] },
|
|
22
|
+
include: ["content"],
|
|
23
|
+
};
|
|
24
|
+
expect(buildManifest({ ...base, build }).build).toEqual(build);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("no build block, or an empty one, adds nothing", () => {
|
|
28
|
+
expect("build" in buildManifest(base)).toBe(false);
|
|
29
|
+
expect("build" in buildManifest({ ...base, build: {} })).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("invalid values throw with the field name", () => {
|
|
33
|
+
const bad: Array<[unknown, string]> = [
|
|
34
|
+
[{ target: [] }, "build.target"],
|
|
35
|
+
[{ target: [""] }, "build.target"],
|
|
36
|
+
[{ polyfill: "all" }, "build.polyfill"],
|
|
37
|
+
[{ polyfill: "usage" }, "polyfill needs build.target"],
|
|
38
|
+
[{ css: { target: 5 } }, "build.css.target"],
|
|
39
|
+
[{ sourcemap: "yes" }, "build.sourcemap"],
|
|
40
|
+
[{ server: { bundle: "no" } }, "build.server.bundle"],
|
|
41
|
+
[{ server: { external: [""] } }, "build.server.external"],
|
|
42
|
+
[{ include: "content" }, "build.include"],
|
|
43
|
+
];
|
|
44
|
+
for (const [build, msg] of bad) {
|
|
45
|
+
expect(() => buildManifest({ ...base, build: build as any })).toThrow(msg);
|
|
46
|
+
}
|
|
47
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -873,6 +873,95 @@ export interface AppManifest {
|
|
|
873
873
|
* `pylon deploy` refuses to ship when one is missing from the
|
|
874
874
|
* project's secrets. */
|
|
875
875
|
requiredEnv?: ManifestRequiredEnv[];
|
|
876
|
+
/** Production build settings (`pylon build`). */
|
|
877
|
+
build?: BuildConfig;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Production build settings for `pylon build`. Dev (`pylon dev`) ignores the
|
|
882
|
+
* browser settings; they apply to the artifact and to `pylon start` builds.
|
|
883
|
+
*
|
|
884
|
+
* ```ts
|
|
885
|
+
* buildManifest({
|
|
886
|
+
* // ...
|
|
887
|
+
* build: {
|
|
888
|
+
* target: ["chrome >= 90", "safari >= 14", "firefox >= 90"],
|
|
889
|
+
* polyfill: "usage",
|
|
890
|
+
* server: { external: ["sharp"] },
|
|
891
|
+
* },
|
|
892
|
+
* });
|
|
893
|
+
* ```
|
|
894
|
+
*/
|
|
895
|
+
export interface BuildConfig {
|
|
896
|
+
/** Browser target for client JavaScript: an ECMAScript edition
|
|
897
|
+
* (`"es2018"` to `"es2022"`) or browserslist queries. Unset: modern
|
|
898
|
+
* output, no lowering. Needs `@swc/core` and `browserslist` in the app's
|
|
899
|
+
* devDependencies. The client runtime needs ES modules and dynamic
|
|
900
|
+
* `import()`, so browsers older than Chrome 63, Firefox 67, and Safari
|
|
901
|
+
* 11.1 cannot run it at any target. */
|
|
902
|
+
target?: string | string[];
|
|
903
|
+
/** core-js polyfills for `target`. `"usage"` adds the features the bundle
|
|
904
|
+
* uses; `"entry"` adds every feature the target lacks. Needs `core-js` in
|
|
905
|
+
* the app's dependencies. Default `false`. */
|
|
906
|
+
polyfill?: false | "usage" | "entry";
|
|
907
|
+
css?: {
|
|
908
|
+
/** CSS target (prefixes, nesting, color syntax), same forms as
|
|
909
|
+
* `target`. Default: `target`. Needs `lightningcss` and `browserslist`. */
|
|
910
|
+
target?: string | string[];
|
|
911
|
+
};
|
|
912
|
+
/** Emit source maps for the client and server bundles. Default `false`. */
|
|
913
|
+
sourcemap?: boolean;
|
|
914
|
+
server?: {
|
|
915
|
+
/** Bundle npm packages into the server output. `false` ships every
|
|
916
|
+
* dependency in `server/node_modules`. Default `true`. */
|
|
917
|
+
bundle?: boolean;
|
|
918
|
+
/** Packages to leave out of the server bundle and ship in
|
|
919
|
+
* `server/node_modules` with their dependencies: native modules and
|
|
920
|
+
* packages that read their own files at run time. */
|
|
921
|
+
external?: string[];
|
|
922
|
+
};
|
|
923
|
+
/** Project files or directories to copy into the artifact, for files the
|
|
924
|
+
* app reads from disk at run time (content/, data/). `public/` and image
|
|
925
|
+
* files under `app/` are always copied. */
|
|
926
|
+
include?: string[];
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/** Throws when `build` has a value `pylon build` cannot use. */
|
|
930
|
+
function validateBuildConfig(build: BuildConfig): void {
|
|
931
|
+
const fail = (msg: string): never => {
|
|
932
|
+
throw new Error(`buildManifest: build.${msg}`);
|
|
933
|
+
};
|
|
934
|
+
const checkTarget = (t: unknown, field: string) => {
|
|
935
|
+
if (t === undefined) return;
|
|
936
|
+
const list = Array.isArray(t) ? t : [t];
|
|
937
|
+
if (list.length === 0 || list.some((x) => typeof x !== "string" || x.trim() === "")) {
|
|
938
|
+
fail(`${field} must be a non-empty string or a list of non-empty strings`);
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
checkTarget(build.target, "target");
|
|
942
|
+
checkTarget(build.css?.target, "css.target");
|
|
943
|
+
const pf = build.polyfill;
|
|
944
|
+
if (pf !== undefined && pf !== false && pf !== "usage" && pf !== "entry") {
|
|
945
|
+
fail(`polyfill must be false, "usage", or "entry"`);
|
|
946
|
+
}
|
|
947
|
+
if (pf && build.target === undefined) {
|
|
948
|
+
fail(`polyfill needs build.target (the browsers to polyfill for)`);
|
|
949
|
+
}
|
|
950
|
+
if (build.sourcemap !== undefined && typeof build.sourcemap !== "boolean") {
|
|
951
|
+
fail(`sourcemap must be a boolean`);
|
|
952
|
+
}
|
|
953
|
+
if (build.server?.bundle !== undefined && typeof build.server.bundle !== "boolean") {
|
|
954
|
+
fail(`server.bundle must be a boolean`);
|
|
955
|
+
}
|
|
956
|
+
for (const [field, list] of [
|
|
957
|
+
["server.external", build.server?.external],
|
|
958
|
+
["include", build.include],
|
|
959
|
+
] as const) {
|
|
960
|
+
if (list === undefined) continue;
|
|
961
|
+
if (!Array.isArray(list) || list.some((x) => typeof x !== "string" || x === "")) {
|
|
962
|
+
fail(`${field} must be a list of non-empty strings`);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
876
965
|
}
|
|
877
966
|
|
|
878
967
|
/** One environment variable the app declares it needs. */
|
|
@@ -1166,11 +1255,19 @@ export async function discoverAppRoutes(opts?: {
|
|
|
1166
1255
|
|
|
1167
1256
|
// Resolve the first existing `<base>.{tsx,ts,jsx,js}` in `dir` and
|
|
1168
1257
|
// return it as a cwd-relative, extension-less module path (or null).
|
|
1258
|
+
// Component paths always use "/", on Windows too: they are manifest keys,
|
|
1259
|
+
// client-bundle import specifiers, and the input to the SSR runtime's
|
|
1260
|
+
// boundary walk, all of which split on "/".
|
|
1169
1261
|
const findModule = (dir: string, base: string): string | null => {
|
|
1170
1262
|
const hit = [`${base}.tsx`, `${base}.ts`, `${base}.jsx`, `${base}.js`]
|
|
1171
1263
|
.map((n: string) => path.join(dir, n))
|
|
1172
1264
|
.find((p: string) => fs.existsSync(p));
|
|
1173
|
-
return hit
|
|
1265
|
+
return hit
|
|
1266
|
+
? path
|
|
1267
|
+
.relative(cwd, hit)
|
|
1268
|
+
.replace(/\.(tsx?|jsx?)$/, "")
|
|
1269
|
+
.replace(/\\/g, "/")
|
|
1270
|
+
: null;
|
|
1174
1271
|
};
|
|
1175
1272
|
|
|
1176
1273
|
function walk(dir: string, segments: string[], layouts: string[]): void {
|
|
@@ -2142,6 +2239,8 @@ export function buildManifest(options: {
|
|
|
2142
2239
|
* ```
|
|
2143
2240
|
*/
|
|
2144
2241
|
requiredEnv?: ManifestRequiredEnv[];
|
|
2242
|
+
/** Production build settings for `pylon build`. See `BuildConfig`. */
|
|
2243
|
+
build?: BuildConfig;
|
|
2145
2244
|
/** Set by `discoverFunctions()` (spread its result into this call).
|
|
2146
2245
|
* When true, the framework's AgentRun/AgentMessage entities and
|
|
2147
2246
|
* their owner-scoping policies are appended to the manifest —
|
|
@@ -2162,6 +2261,7 @@ export function buildManifest(options: {
|
|
|
2162
2261
|
// can `.policies(policy({ allowRead: "..." }))` without one. Auto-
|
|
2163
2262
|
// derive from the entity + a counter so two attached policies
|
|
2164
2263
|
// don't collide.
|
|
2264
|
+
if (options.build) validateBuildConfig(options.build);
|
|
2165
2265
|
const attached: PolicyDefinition[] = [];
|
|
2166
2266
|
for (const ent of options.entities) {
|
|
2167
2267
|
const extracted = extractAttachedPolicies(ent);
|
|
@@ -2225,6 +2325,9 @@ export function buildManifest(options: {
|
|
|
2225
2325
|
...(options.requiredEnv && options.requiredEnv.length > 0
|
|
2226
2326
|
? { requiredEnv: options.requiredEnv }
|
|
2227
2327
|
: {}),
|
|
2328
|
+
...(options.build && Object.keys(options.build).length > 0
|
|
2329
|
+
? { build: options.build }
|
|
2330
|
+
: {}),
|
|
2228
2331
|
};
|
|
2229
2332
|
}
|
|
2230
2333
|
|