create-monolayer-app 0.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-monolayer-app",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "CLI to scaffold a new monolayer application",
5
5
  "keywords": [
6
6
  "create-app",
@@ -44,6 +44,16 @@ npm run build
44
44
 
45
45
  ## Deployment
46
46
 
47
+ ### Application Source Archive
48
+
49
+ Create the source archive with this command:
50
+
51
+ ```bash
52
+ npm run package-app
53
+ ```
54
+
55
+ The packager writes a `.tar.gz` archive into `build/`. It excludes macOS metadata: `._*` files, `.DS_Store` files, and `__MACOSX` directories. These exclusions also apply with custom exclusion settings. The packager disables macOS metadata generation in `tar`.
56
+
47
57
  ### Docker Deployment
48
58
 
49
59
  To build and run using Docker:
@@ -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
+ });
@@ -12,7 +12,12 @@ export default defineConfig({
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"],