create-monolayer-app 0.5.1 → 0.6.3

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 (87) hide show
  1. package/bin/index.js +91 -59
  2. package/package.json +19 -16
  3. package/template/.agents/skills/{monolayer-application-permissions → application-permissions}/SKILL.md +2 -2
  4. package/template/.agents/skills/application-permissions/agents/openai.yaml +4 -0
  5. package/template/.agents/skills/kysely/SKILL.md +48 -0
  6. package/template/.agents/skills/kysely/agents/openai.yaml +4 -0
  7. package/template/.agents/skills/kysely/references/execution-plugins-and-runtime.md +67 -0
  8. package/template/.agents/skills/kysely/references/expressions-and-helpers.md +102 -0
  9. package/template/.agents/skills/kysely/references/migrations.md +43 -0
  10. package/template/.agents/skills/kysely/references/official-docs-map.md +97 -0
  11. package/template/.agents/skills/kysely/references/query-building.md +119 -0
  12. package/template/.agents/skills/kysely/references/transactions.md +47 -0
  13. package/template/.agents/skills/kysely/references/troubleshooting-and-boundaries.md +67 -0
  14. package/template/.agents/skills/kysely/references/types.md +58 -0
  15. package/template/.agents/skills/persistent-app-data/SKILL.md +74 -0
  16. package/template/.agents/skills/persistent-app-data/agents/openai.yaml +4 -0
  17. package/template/.agents/skills/persistent-app-data/references/generated-types.md +23 -0
  18. package/template/.agents/skills/persistent-app-data/references/migrations.md +68 -0
  19. package/template/.agents/skills/vitest/GENERATION.md +25 -0
  20. package/template/.agents/skills/vitest/SKILL.md +59 -0
  21. package/template/.agents/skills/vitest/references/advanced-environments.md +26 -0
  22. package/template/.agents/skills/vitest/references/advanced-projects.md +31 -0
  23. package/template/.agents/skills/vitest/references/advanced-type-testing.md +30 -0
  24. package/template/.agents/skills/vitest/references/advanced-vi.md +23 -0
  25. package/template/.agents/skills/vitest/references/core-cli.md +23 -0
  26. package/template/.agents/skills/vitest/references/core-config.md +24 -0
  27. package/template/.agents/skills/vitest/references/core-describe.md +24 -0
  28. package/template/.agents/skills/vitest/references/core-expect.md +35 -0
  29. package/template/.agents/skills/vitest/references/core-hooks.md +26 -0
  30. package/template/.agents/skills/vitest/references/core-test-api.md +29 -0
  31. package/template/.agents/skills/vitest/references/features-benchmarking.md +25 -0
  32. package/template/.agents/skills/vitest/references/features-concurrency.md +20 -0
  33. package/template/.agents/skills/vitest/references/features-context.md +27 -0
  34. package/template/.agents/skills/vitest/references/features-coverage.md +17 -0
  35. package/template/.agents/skills/vitest/references/features-filtering.md +24 -0
  36. package/template/.agents/skills/vitest/references/features-mocking.md +32 -0
  37. package/template/.agents/skills/vitest/references/features-reporters.md +22 -0
  38. package/template/.agents/skills/vitest/references/features-snapshots.md +23 -0
  39. package/template/.agents/skills/vitest/references/features-test-tags.md +30 -0
  40. package/template/.agents/skills/vitest/references/version-compatibility.md +19 -0
  41. package/template/.claude/CLAUDE.md +2 -2
  42. package/template/.react-router/types/+routes.ts +41 -0
  43. package/template/.react-router/types/+server-build.d.ts +18 -0
  44. package/template/.react-router/types/app/+types/root.ts +68 -0
  45. package/template/.react-router/types/app/routes/+types/files.ts +71 -0
  46. package/template/.react-router/types/app/routes/+types/home.ts +71 -0
  47. package/template/AGENTS.md +6 -2
  48. package/template/README.md +10 -0
  49. package/template/app/routes/files.ts +3 -3
  50. package/template/build/client/assets/entry.client-Bc58m2-m.js +9 -0
  51. package/template/build/client/assets/errorBoundaries-CQxPIuVU.js +16 -0
  52. package/template/build/client/assets/files-BvRk9kiK.js +0 -0
  53. package/template/build/client/assets/home-CH2INc5e.js +1 -0
  54. package/template/build/client/assets/jsx-runtime-eLCqVKHJ.js +1 -0
  55. package/template/build/client/assets/logo-dark-pX2395Y0.svg +23 -0
  56. package/template/build/client/assets/logo-light-CVbx2LBR.svg +23 -0
  57. package/template/build/client/assets/manifest-2d65dd0f.js +1 -0
  58. package/template/build/client/assets/root-BeNS6njP.css +2 -0
  59. package/template/build/client/assets/root-C1tmjS9o.js +1 -0
  60. package/template/build/client/favicon.ico +0 -0
  61. package/template/build/server/index.js +1096 -0
  62. package/template/lib/db/client.ts +1 -27
  63. package/template/package-lock.json +8221 -0
  64. package/template/package.json +7 -3
  65. package/template/vendor/file-bucket/{client.ts → index.client.ts} +11 -9
  66. package/template/vendor/file-bucket/{server.ts → index.server.ts} +13 -8
  67. package/template/vendor/file-bucket/{index.ts → lib/file-bucket.ts} +2 -1
  68. package/template/vendor/file-bucket/{react-router.ts → react-router.server.ts} +15 -13
  69. package/template/vendor/kysely-dsql/dsql/dsql-kysely.ts +31 -3
  70. package/template/vendor/kysely-dsql/generator/migration-file.ts +3 -3
  71. package/template/vendor/kysely-dsql/plugins/index.ts +0 -2
  72. package/template/vendor/package-app/package-app.mjs +10 -3
  73. package/template/vendor/package-app/package-app.test.mjs +167 -0
  74. package/template/vite.config.ts +3 -1
  75. package/template/vitest.config.ts +7 -2
  76. package/template/.agents/skills/monolayer-application-permissions/agents/openai.yaml +0 -4
  77. package/template/vendor/kysely-dsql/plugins/soft-delete.plugin.ts +0 -217
  78. /package/template/.agents/skills/{monolayer-application-permissions → application-permissions}/references/development-and-verification.md +0 -0
  79. /package/template/.agents/skills/{monolayer-application-permissions → application-permissions}/references/interface-examples.md +0 -0
  80. /package/template/.agents/skills/{monolayer-application-permissions → application-permissions}/references/server-examples.md +0 -0
  81. /package/template/vendor/file-bucket/{errors.ts → lib/errors.server.ts} +0 -0
  82. /package/template/vendor/file-bucket/{multipart.ts → lib/multipart.ts} +0 -0
  83. /package/template/vendor/file-bucket/{protocol.ts → lib/protocol.ts} +0 -0
  84. /package/template/vendor/file-bucket/{transport.ts → lib/transport.client.ts} +0 -0
  85. /package/template/vendor/file-bucket/{types.ts → lib/types.ts} +0 -0
  86. /package/template/vendor/file-bucket/{transfers.ts → lib/upload-signed-url.ts} +0 -0
  87. /package/template/vendor/file-bucket/{xml.ts → lib/xml.ts} +0 -0
@@ -20,7 +20,10 @@
20
20
  "package-app": "node vendor/package-app/package-app.mjs"
21
21
  },
22
22
  "dependencies": {
23
+ "@aws-sdk/client-s3": "3.1152.0",
23
24
  "@aws-sdk/dsql-signer": "^3.1126.0",
25
+ "@aws-sdk/lib-storage": "^3.1152.0",
26
+ "@aws-sdk/s3-request-presigner": "3.1152.0",
24
27
  "@aws/aurora-dsql-node-postgres-connector": "^0.1.9",
25
28
  "@kysera/infra": "^0.10.0",
26
29
  "@react-router/node": "^8",
@@ -31,7 +34,8 @@
31
34
  "pg": "^8.23.0",
32
35
  "react": "^19.2.7",
33
36
  "react-dom": "^19.2.7",
34
- "react-router": "^8"
37
+ "react-router": "^8",
38
+ "zod": "^4.6.7"
35
39
  },
36
40
  "devDependencies": {
37
41
  "@electric-sql/pglite": "^0.5.8",
@@ -42,8 +46,8 @@
42
46
  "@types/react": "^19.2.14",
43
47
  "@types/react-dom": "^19.2.3",
44
48
  "kysely-ctl": "^0.21.0",
45
- "oxfmt": "^0.65.0",
46
- "oxlint": "^1.80.0",
49
+ "oxfmt": "^0.73.0",
50
+ "oxlint": "^1.88.0",
47
51
  "oxlint-plugin-react-doctor": "^0.9.12",
48
52
  "tailwindcss": "^4.2.2",
49
53
  "tsx": "^4.23.13",
@@ -1,11 +1,12 @@
1
- import type { FileBucket } from "./index.js";
2
- import { createMultipartUploadClient } from "./multipart.js";
3
- import { uploadSignedUrl } from "./transfers.js";
4
- import { request } from "./transport.js";
5
- import type { GetUrlOptions, PutOptions, PutUrlOptions } from "./types.js";
1
+ import type { FileBucket } from "./lib/file-bucket.js";
2
+ import { createMultipartUploadClient } from "./lib/multipart.js";
3
+ import { request } from "./lib/transport.client.js";
4
+ import type { GetUrlOptions, PutOptions, PutUrlOptions } from "./lib/types.js";
5
+ import { uploadSignedUrl } from "./lib/upload-signed-url.js";
6
6
 
7
- export { createMultipartUploadClient } from "./multipart.js";
8
- export type { UploadGrant } from "./protocol.js";
7
+ export { FileBucket } from "./lib/file-bucket.js";
8
+ export { createMultipartUploadClient } from "./lib/multipart.js";
9
+ export type { UploadGrant } from "./lib/protocol.js";
9
10
  export type {
10
11
  FileMultipartUploadDescriptor,
11
12
  FileMultipartUploadClient,
@@ -14,7 +15,8 @@ export type {
14
15
  GetUrlOptions,
15
16
  PutOptions,
16
17
  PutUrlOptions,
17
- } from "./types.js";
18
+ } from "./lib/types.js";
19
+
18
20
  export type BrowserUploadBody =
19
21
  | Blob
20
22
  | string
@@ -32,7 +34,7 @@ export type BrowserUploadBody =
32
34
  * @param options - Optional settings including an `AbortSignal`.
33
35
  * @returns A promise that resolves when the deletion is completed by the server.
34
36
  */
35
- function deleteFile(
37
+ export function deleteFile(
36
38
  bucket: FileBucket,
37
39
  path: string,
38
40
  options: { signal?: AbortSignal } = {}
@@ -16,9 +16,11 @@ import {
16
16
  import { Upload } from "@aws-sdk/lib-storage";
17
17
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
18
18
 
19
- import type { FileBucket } from "./index.js";
20
- import { createMultipartUploadClient, multipartPartSize } from "./multipart.js";
21
- import { uploadSignedUrl } from "./transfers.js";
19
+ import type { FileBucket } from "./lib/file-bucket.js";
20
+ import {
21
+ createMultipartUploadClient,
22
+ multipartPartSize,
23
+ } from "./lib/multipart.js";
22
24
  import type {
23
25
  FileUploadBody,
24
26
  MultipartUploadOptions,
@@ -31,10 +33,11 @@ import type {
31
33
  FileInfo,
32
34
  FileDownloadClient,
33
35
  FileUploadClient,
34
- } from "./types.js";
36
+ } from "./lib/types.js";
37
+ import { uploadSignedUrl } from "./lib/upload-signed-url.js";
35
38
 
36
39
  // Re-export public types
37
- export { FileBucketErrors } from "./errors.js";
40
+ export { FileBucketErrors } from "./lib/errors.server.js";
38
41
  export type {
39
42
  FileUploadBody,
40
43
  MultipartUploadOptions,
@@ -52,7 +55,9 @@ export type {
52
55
  FileUploadClient,
53
56
  FileDownloadDescriptor,
54
57
  FileUploadDescriptor,
55
- } from "./types.js";
58
+ } from "./lib/types.js";
59
+
60
+ export { FileBucket } from "./lib/file-bucket.js";
56
61
 
57
62
  function uploadBodySize(body: FileUploadBody): number | undefined {
58
63
  // The public body union includes strings, byte arrays, blobs, and streams.
@@ -159,7 +164,7 @@ export async function put(
159
164
  });
160
165
  options?.signal?.addEventListener("abort", abort, { once: true });
161
166
  if (options?.onProgress) {
162
- upload.on("httpUploadProgress", (progress) => {
167
+ upload.on("httpUploadProgress", (progress: any) => {
163
168
  options.onProgress?.({
164
169
  uploadedBytes: progress.loaded ?? 0,
165
170
  totalBytes: progress.total,
@@ -270,7 +275,7 @@ export async function deleteBatch(
270
275
  if (result.Errors?.length) {
271
276
  throw new AggregateError(
272
277
  result.Errors.map(
273
- (error) =>
278
+ (error: any) =>
274
279
  new Error(
275
280
  `Failed to delete ${error.Key}: ${error.Code} ${error.Message ?? ""}`
276
281
  )
@@ -1,6 +1,7 @@
1
- /** Shared file bucket definition. Safe to import on the server and in the browser. */
1
+ /** Shared file bucket definition for server and browser code. */
2
2
  export class FileBucket {
3
3
  readonly name: string;
4
+
4
5
  constructor(name: string) {
5
6
  this.name = name;
6
7
  }
@@ -1,18 +1,20 @@
1
- /* oxlint-disable no-await-in-loop, anti-slop/no-unknown-returns, anti-slop/no-runtime-typeof */
2
- import { FileBucket } from "./index.js";
3
- import { parseFileBucketRequest } from "./protocol.js";
4
- import type {
5
- FileBucketRequest,
6
- FileBucketResponses,
7
- UploadGrant,
8
- } from "./protocol.js";
9
1
  import {
10
2
  getUrl,
11
3
  putUrl,
12
4
  delete as deleteFile,
13
5
  deleteBatch,
14
6
  createMultipartUploadHandle,
15
- } from "./server.js";
7
+ } from "./index.server.js";
8
+ /* oxlint-disable no-await-in-loop, anti-slop/no-unknown-returns, anti-slop/no-runtime-typeof */
9
+ import { FileBucket } from "./lib/file-bucket.js";
10
+ import { parseFileBucketRequest } from "./lib/protocol.js";
11
+ import type {
12
+ FileBucketRequest,
13
+ FileBucketResponses,
14
+ UploadGrant,
15
+ } from "./lib/protocol.js";
16
+
17
+ export { parseFileBucketRequest };
16
18
 
17
19
  const MULTIPART_THRESHOLD = 8 * 1024 * 1024;
18
20
  export const MAX_REQUEST_BYTES = 64 * 1024;
@@ -24,11 +26,11 @@ export type {
24
26
  UploadGrant,
25
27
  SignedUrl,
26
28
  FileBucketResponses,
27
- } from "./protocol.js";
29
+ } from "./lib/protocol.js";
28
30
 
29
31
  export async function readLimitedJson(
30
32
  request: Request,
31
- maxBytes: number,
33
+ maxBytes: number
32
34
  ): Promise<unknown> {
33
35
  if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) {
34
36
  throw new RangeError("maxBytes must be a positive safe integer.");
@@ -72,7 +74,7 @@ export async function readLimitedJson(
72
74
 
73
75
  async function uploadGrant(
74
76
  bucket: FileBucket,
75
- input: Extract<FileBucketRequest, { operation: "put" }>,
77
+ input: Extract<FileBucketRequest, { operation: "put" }>
76
78
  ): Promise<UploadGrant> {
77
79
  const options = {
78
80
  contentType: input.contentType,
@@ -95,7 +97,7 @@ async function uploadGrant(
95
97
 
96
98
  export async function dispatch(
97
99
  bucket: FileBucket,
98
- input: FileBucketRequest,
100
+ input: FileBucketRequest
99
101
  ): Promise<FileBucketResponses[FileBucketRequest["operation"]]> {
100
102
  switch (input.operation) {
101
103
  case "delete": {
@@ -8,8 +8,14 @@
8
8
  * ```
9
9
  */
10
10
 
11
- import { Kysely, type KyselyConfig } from "kysely";
11
+ import assert from "node:assert";
12
12
 
13
+ import { PGlite } from "@electric-sql/pglite";
14
+ import { Kysely, PGliteDialect, type KyselyConfig } from "kysely";
15
+
16
+ import plugins from "../plugins";
17
+ import { DSQLDialect } from "./dialect";
18
+ import { createDSQLPool } from "./pool";
13
19
  import { DSQLSchemaModule } from "./schema-module";
14
20
 
15
21
  // eslint-disable-next-line max-classes-per-file
@@ -72,8 +78,15 @@ export class DSQLKysely<DB> extends Kysely<DB> {
72
78
  */
73
79
  // The explicit constructor documents the public configuration contract.
74
80
  // eslint-disable-next-line no-useless-constructor
75
- constructor(config: KyselyConfig) {
76
- super(config);
81
+ constructor(config?: KyselyConfig) {
82
+ if (config) {
83
+ super(config);
84
+ } else {
85
+ super({
86
+ dialect: dialect(),
87
+ plugins,
88
+ });
89
+ }
77
90
  }
78
91
 
79
92
  /**
@@ -91,3 +104,18 @@ export class DSQLKysely<DB> extends Kysely<DB> {
91
104
  return new DSQLSchemaModule(this.getExecutor());
92
105
  }
93
106
  }
107
+
108
+ function dialect() {
109
+ if (process.env.NODE_ENV === "production") {
110
+ const endpoint = process.env.CLUSTER_ENDPOINT;
111
+ assert.ok(endpoint);
112
+ return new DSQLDialect({
113
+ pool: createDSQLPool(endpoint),
114
+ });
115
+ }
116
+ return new PGliteDialect({
117
+ pglite: new PGlite(
118
+ `./.pglite_${process.env.NODE_ENV === "test" ? "test" : "development"}`
119
+ ),
120
+ });
121
+ }
@@ -51,11 +51,11 @@ export interface CreateMigrationFileOptions {
51
51
  * ```
52
52
  */
53
53
  export async function createMigrationFile(
54
- options: CreateMigrationFileOptions,
54
+ options: CreateMigrationFileOptions
55
55
  ): Promise<string> {
56
56
  if (!MIGRATION_NAME_PATTERN.test(options.name)) {
57
57
  throw new Error(
58
- "Migration name must use lowercase letters, digits, and single hyphens.",
58
+ "Migration name must use lowercase letters, digits, and single hyphens."
59
59
  );
60
60
  }
61
61
 
@@ -63,7 +63,7 @@ export async function createMigrationFile(
63
63
  const migrationDirectory = path.resolve(options.migrationDirectory);
64
64
  const filePath = path.join(
65
65
  migrationDirectory,
66
- `${timestamp}_${options.name}.ts`,
66
+ `${timestamp}_${options.name}.ts`
67
67
  );
68
68
 
69
69
  await mkdir(migrationDirectory, { recursive: true });
@@ -1,10 +1,8 @@
1
1
  import { CamelCasePlugin } from "kysely";
2
2
 
3
3
  import { AutoUpdatedAtPlugin } from "./auto-updated-at.plugin";
4
- import { SoftDelete } from "./soft-delete.plugin";
5
4
 
6
5
  export default [
7
6
  new CamelCasePlugin(),
8
- new SoftDelete({ ignoredTables: [] }),
9
7
  new AutoUpdatedAtPlugin({ ignoredTables: [] }),
10
8
  ];
@@ -19,12 +19,12 @@ function getPackageName(cwd) {
19
19
  if (existsSync(packageJsonPath)) {
20
20
  try {
21
21
  const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
22
- // oxlint-disable-next-line anti-slop/no-runtime-typeof
22
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- Use the package name only when it is a nonempty string.
23
23
  if (typeof pkg.name === "string" && pkg.name.trim()) {
24
24
  return pkg.name.trim().replace(/^@/u, "").replaceAll("/", "-");
25
25
  }
26
26
  } catch {
27
- // Ignore parse errors and use fallback
27
+ return "app";
28
28
  }
29
29
  }
30
30
  return "app";
@@ -64,7 +64,13 @@ export function packageApp(options = {}) {
64
64
  const gitignoreFile = options.gitignorePath ?? DEFAULT_GITIGNORE;
65
65
  const excludes = options.excludes ?? DEFAULT_EXCLUDES;
66
66
 
67
- const tarArgs = ["-czf", relativePath];
67
+ const tarArgs = [
68
+ "-czf",
69
+ relativePath,
70
+ "--exclude=._*",
71
+ "--exclude=__MACOSX",
72
+ "--exclude=.DS_Store",
73
+ ];
68
74
 
69
75
  const fullGitignorePath = path.resolve(cwd, gitignoreFile);
70
76
  if (existsSync(fullGitignorePath)) {
@@ -79,6 +85,7 @@ export function packageApp(options = {}) {
79
85
 
80
86
  const result = spawnSync("tar", tarArgs, {
81
87
  cwd,
88
+ env: { ...process.env, COPYFILE_DISABLE: "1" },
82
89
  stdio: "inherit",
83
90
  });
84
91
 
@@ -0,0 +1,167 @@
1
+ import { execFileSync, spawnSync } from "node:child_process";
2
+ import {
3
+ existsSync,
4
+ mkdirSync,
5
+ mkdtempSync,
6
+ readdirSync,
7
+ rmSync,
8
+ writeFileSync,
9
+ } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import path from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+
14
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
15
+
16
+ import { packageApp } from "./package-app.mjs";
17
+
18
+ const migrationPath = "lib/db/migrations/1792314123321_add-tasks.ts";
19
+ const migration =
20
+ "export async function up(db) { await db.schema.createTable('tasks').execute(); }";
21
+ const metadata = Buffer.from([0, 5, 22, 7, 0, 2, 0, 0]);
22
+ const packager = fileURLToPath(new URL("package-app.mjs", import.meta.url));
23
+
24
+ const writeFiles = (root, files) => {
25
+ for (const [name, content] of Object.entries(files)) {
26
+ const destination = path.join(root, name);
27
+ mkdirSync(path.dirname(destination), { recursive: true });
28
+ writeFileSync(destination, content);
29
+ }
30
+ };
31
+
32
+ const archivePaths = (archive) => {
33
+ const args = ["-tzf", archive];
34
+ if (process.platform === "darwin") {
35
+ // macOS tar otherwise hides AppleDouble entries during archive inspection.
36
+ args.unshift("--options=!mac-ext");
37
+ }
38
+ return execFileSync("tar", args, { encoding: "utf-8" }).trim().split("\n");
39
+ };
40
+
41
+ const metadataPaths = (paths) =>
42
+ paths.filter((name) =>
43
+ name
44
+ .split("/")
45
+ .some(
46
+ (part) =>
47
+ part.startsWith("._") || part === "__MACOSX" || part === ".DS_Store"
48
+ )
49
+ );
50
+
51
+ describe("application source packaging", () => {
52
+ let fixture;
53
+ let application;
54
+
55
+ beforeEach(() => {
56
+ fixture = mkdtempSync(path.join(tmpdir(), "application-packager-"));
57
+ application = path.join(fixture, "app");
58
+ writeFiles(application, {
59
+ "package.json": '{"name":"packaging-fixture"}',
60
+ ".gitignore": "ignored.txt\n",
61
+ "ignored.txt": "ignored by Git",
62
+ "private.txt": "excluded by the caller",
63
+ ".config/kysely.config.ts": "export default {};",
64
+ [migrationPath]: migration,
65
+ "lib/db/migrations/._1792314123321_add-tasks.ts": metadata,
66
+ "._package.json": metadata,
67
+ ".DS_Store": metadata,
68
+ "lib/.DS_Store": metadata,
69
+ "__MACOSX/._package.json": metadata,
70
+ "lib/__MACOSX/metadata": metadata,
71
+ });
72
+ });
73
+
74
+ afterEach(() => {
75
+ rmSync(fixture, { force: true, recursive: true });
76
+ });
77
+
78
+ it.each([
79
+ ["default exclusions", {}],
80
+ ["custom exclusions", { excludes: ["build", "private.txt"] }],
81
+ ])(
82
+ "excludes metadata with %s and preserves migration source",
83
+ (_name, options) => {
84
+ const archive = packageApp({ ...options, cwd: application });
85
+ const paths = archivePaths(archive.absolutePath);
86
+
87
+ expect(metadataPaths(paths)).toStrictEqual([]);
88
+ expect(paths).toContain(`./${migrationPath}`);
89
+ expect(paths).toContain("./.config/kysely.config.ts");
90
+ expect(paths).not.toContain("./ignored.txt");
91
+ expect(
92
+ execFileSync(
93
+ "tar",
94
+ ["-xOf", archive.absolutePath, `./${migrationPath}`],
95
+ {
96
+ encoding: "utf-8",
97
+ }
98
+ )
99
+ ).toBe(migration);
100
+ }
101
+ );
102
+
103
+ it("preserves caller exclusions without deleting source metadata", () => {
104
+ const archive = packageApp({
105
+ cwd: application,
106
+ excludes: ["build", "private.txt"],
107
+ });
108
+
109
+ expect(archivePaths(archive.absolutePath)).not.toContain("./private.txt");
110
+ expect(
111
+ existsSync(
112
+ path.join(application, "lib/db/migrations/._1792314123321_add-tasks.ts")
113
+ )
114
+ ).toBe(true);
115
+ });
116
+
117
+ it.runIf(process.platform === "darwin")(
118
+ "does not generate metadata from macOS extended attributes",
119
+ () => {
120
+ const source = path.join(fixture, "attributes");
121
+ writeFiles(source, { [migrationPath]: migration });
122
+ execFileSync("xattr", [
123
+ "-w",
124
+ "com.monolayer.packaging-test",
125
+ "fixture",
126
+ path.join(source, migrationPath),
127
+ ]);
128
+ const control = path.join(fixture, "control.tar.gz");
129
+ execFileSync("env", [
130
+ "-u",
131
+ "COPYFILE_DISABLE",
132
+ "tar",
133
+ "-czf",
134
+ control,
135
+ "-C",
136
+ source,
137
+ ".",
138
+ ]);
139
+
140
+ const archive = packageApp({ cwd: source });
141
+
142
+ expect(metadataPaths(archivePaths(control))).toContain(
143
+ "./lib/db/migrations/._1792314123321_add-tasks.ts"
144
+ );
145
+ expect(metadataPaths(archivePaths(archive.absolutePath))).toStrictEqual(
146
+ []
147
+ );
148
+ expect(archivePaths(archive.absolutePath)).toContain(
149
+ `./${migrationPath}`
150
+ );
151
+ }
152
+ );
153
+
154
+ it("creates a clean archive through the package-app command", () => {
155
+ const result = spawnSync(process.execPath, [packager], {
156
+ cwd: application,
157
+ encoding: "utf-8",
158
+ });
159
+ const [name] = readdirSync(path.join(application, "build"));
160
+
161
+ expect(result.status).toBe(0);
162
+ expect(result.stdout).toContain("Successfully packaged application");
163
+ expect(
164
+ metadataPaths(archivePaths(path.join(application, "build", name)))
165
+ ).toStrictEqual([]);
166
+ });
167
+ });
@@ -2,8 +2,10 @@ import { reactRouter } from "@react-router/dev/vite";
2
2
  import tailwindcss from "@tailwindcss/vite";
3
3
  import { defineConfig } from "vite";
4
4
 
5
+ import { s3Emulator } from "./vendor/file-bucket/vite.js";
6
+
5
7
  export default defineConfig({
6
- plugins: [tailwindcss(), reactRouter()],
8
+ plugins: [tailwindcss(), reactRouter(), s3Emulator()],
7
9
  resolve: {
8
10
  tsconfigPaths: true,
9
11
  },
@@ -6,13 +6,18 @@ export default defineConfig({
6
6
  resolve: {
7
7
  alias: {
8
8
  "virtual:db-migrations": fileURLToPath(
9
- new URL("test/fixtures/virtual-db-migrations.ts", import.meta.url),
9
+ new URL("test/fixtures/virtual-db-migrations.ts", import.meta.url)
10
10
  ),
11
11
  },
12
12
  },
13
13
  test: {
14
14
  environment: "node",
15
- include: ["src/**/*.test.ts", "lib/**/*.test.ts", "app/**/*.test.ts"],
15
+ include: [
16
+ "src/**/*.test.ts",
17
+ "lib/**/*.test.ts",
18
+ "app/**/*.test.ts",
19
+ "vendor/package-app/**/*.test.mjs",
20
+ ],
16
21
  coverage: {
17
22
  provider: "v8",
18
23
  reporter: ["text", "json", "html"],
@@ -1,4 +0,0 @@
1
- interface:
2
- display_name: "Monolayer Application Permissions"
3
- short_description: "Use shared application roles in React Router"
4
- default_prompt: "Use $monolayer-application-permissions to apply shared read and write permissions with the existing user middleware."