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.
- package/bin/index.js +91 -59
- package/package.json +19 -16
- package/template/.agents/skills/{monolayer-application-permissions → application-permissions}/SKILL.md +2 -2
- package/template/.agents/skills/application-permissions/agents/openai.yaml +4 -0
- package/template/.agents/skills/kysely/SKILL.md +48 -0
- package/template/.agents/skills/kysely/agents/openai.yaml +4 -0
- package/template/.agents/skills/kysely/references/execution-plugins-and-runtime.md +67 -0
- package/template/.agents/skills/kysely/references/expressions-and-helpers.md +102 -0
- package/template/.agents/skills/kysely/references/migrations.md +43 -0
- package/template/.agents/skills/kysely/references/official-docs-map.md +97 -0
- package/template/.agents/skills/kysely/references/query-building.md +119 -0
- package/template/.agents/skills/kysely/references/transactions.md +47 -0
- package/template/.agents/skills/kysely/references/troubleshooting-and-boundaries.md +67 -0
- package/template/.agents/skills/kysely/references/types.md +58 -0
- package/template/.agents/skills/persistent-app-data/SKILL.md +74 -0
- package/template/.agents/skills/persistent-app-data/agents/openai.yaml +4 -0
- package/template/.agents/skills/persistent-app-data/references/generated-types.md +23 -0
- package/template/.agents/skills/persistent-app-data/references/migrations.md +68 -0
- package/template/.agents/skills/vitest/GENERATION.md +25 -0
- package/template/.agents/skills/vitest/SKILL.md +59 -0
- package/template/.agents/skills/vitest/references/advanced-environments.md +26 -0
- package/template/.agents/skills/vitest/references/advanced-projects.md +31 -0
- package/template/.agents/skills/vitest/references/advanced-type-testing.md +30 -0
- package/template/.agents/skills/vitest/references/advanced-vi.md +23 -0
- package/template/.agents/skills/vitest/references/core-cli.md +23 -0
- package/template/.agents/skills/vitest/references/core-config.md +24 -0
- package/template/.agents/skills/vitest/references/core-describe.md +24 -0
- package/template/.agents/skills/vitest/references/core-expect.md +35 -0
- package/template/.agents/skills/vitest/references/core-hooks.md +26 -0
- package/template/.agents/skills/vitest/references/core-test-api.md +29 -0
- package/template/.agents/skills/vitest/references/features-benchmarking.md +25 -0
- package/template/.agents/skills/vitest/references/features-concurrency.md +20 -0
- package/template/.agents/skills/vitest/references/features-context.md +27 -0
- package/template/.agents/skills/vitest/references/features-coverage.md +17 -0
- package/template/.agents/skills/vitest/references/features-filtering.md +24 -0
- package/template/.agents/skills/vitest/references/features-mocking.md +32 -0
- package/template/.agents/skills/vitest/references/features-reporters.md +22 -0
- package/template/.agents/skills/vitest/references/features-snapshots.md +23 -0
- package/template/.agents/skills/vitest/references/features-test-tags.md +30 -0
- package/template/.agents/skills/vitest/references/version-compatibility.md +19 -0
- package/template/.claude/CLAUDE.md +2 -2
- package/template/.react-router/types/+routes.ts +41 -0
- package/template/.react-router/types/+server-build.d.ts +18 -0
- package/template/.react-router/types/app/+types/root.ts +68 -0
- package/template/.react-router/types/app/routes/+types/files.ts +71 -0
- package/template/.react-router/types/app/routes/+types/home.ts +71 -0
- package/template/AGENTS.md +6 -2
- package/template/README.md +10 -0
- package/template/app/routes/files.ts +3 -3
- package/template/build/client/assets/entry.client-Bc58m2-m.js +9 -0
- package/template/build/client/assets/errorBoundaries-CQxPIuVU.js +16 -0
- package/template/build/client/assets/files-BvRk9kiK.js +0 -0
- package/template/build/client/assets/home-CH2INc5e.js +1 -0
- package/template/build/client/assets/jsx-runtime-eLCqVKHJ.js +1 -0
- package/template/build/client/assets/logo-dark-pX2395Y0.svg +23 -0
- package/template/build/client/assets/logo-light-CVbx2LBR.svg +23 -0
- package/template/build/client/assets/manifest-2d65dd0f.js +1 -0
- package/template/build/client/assets/root-BeNS6njP.css +2 -0
- package/template/build/client/assets/root-C1tmjS9o.js +1 -0
- package/template/build/client/favicon.ico +0 -0
- package/template/build/server/index.js +1096 -0
- package/template/lib/db/client.ts +1 -27
- package/template/package-lock.json +8221 -0
- package/template/package.json +7 -3
- package/template/vendor/file-bucket/{client.ts → index.client.ts} +11 -9
- package/template/vendor/file-bucket/{server.ts → index.server.ts} +13 -8
- package/template/vendor/file-bucket/{index.ts → lib/file-bucket.ts} +2 -1
- package/template/vendor/file-bucket/{react-router.ts → react-router.server.ts} +15 -13
- package/template/vendor/kysely-dsql/dsql/dsql-kysely.ts +31 -3
- package/template/vendor/kysely-dsql/generator/migration-file.ts +3 -3
- package/template/vendor/kysely-dsql/plugins/index.ts +0 -2
- package/template/vendor/package-app/package-app.mjs +10 -3
- package/template/vendor/package-app/package-app.test.mjs +167 -0
- package/template/vite.config.ts +3 -1
- package/template/vitest.config.ts +7 -2
- package/template/.agents/skills/monolayer-application-permissions/agents/openai.yaml +0 -4
- package/template/vendor/kysely-dsql/plugins/soft-delete.plugin.ts +0 -217
- /package/template/.agents/skills/{monolayer-application-permissions → application-permissions}/references/development-and-verification.md +0 -0
- /package/template/.agents/skills/{monolayer-application-permissions → application-permissions}/references/interface-examples.md +0 -0
- /package/template/.agents/skills/{monolayer-application-permissions → application-permissions}/references/server-examples.md +0 -0
- /package/template/vendor/file-bucket/{errors.ts → lib/errors.server.ts} +0 -0
- /package/template/vendor/file-bucket/{multipart.ts → lib/multipart.ts} +0 -0
- /package/template/vendor/file-bucket/{protocol.ts → lib/protocol.ts} +0 -0
- /package/template/vendor/file-bucket/{transport.ts → lib/transport.client.ts} +0 -0
- /package/template/vendor/file-bucket/{types.ts → lib/types.ts} +0 -0
- /package/template/vendor/file-bucket/{transfers.ts → lib/upload-signed-url.ts} +0 -0
- /package/template/vendor/file-bucket/{xml.ts → lib/xml.ts} +0 -0
package/template/package.json
CHANGED
|
@@ -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.
|
|
46
|
-
"oxlint": "^1.
|
|
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 "./
|
|
2
|
-
import { createMultipartUploadClient } from "./multipart.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
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 {
|
|
8
|
-
export
|
|
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 "./
|
|
20
|
-
import {
|
|
21
|
-
|
|
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,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
|
|
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
|
|
76
|
-
|
|
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
|
-
|
|
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 = [
|
|
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
|
+
});
|
package/template/vite.config.ts
CHANGED
|
@@ -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: [
|
|
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"],
|