@usequark/quark-create-app 1.23.12 → 1.23.13

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.
@@ -1,105 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import fs from "node:fs/promises";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { after, test } from "node:test";
6
-
7
- const temporaryDirectories = [];
8
-
9
- after(async () => {
10
- await Promise.all(
11
- temporaryDirectories.map((dir) =>
12
- fs.rm(dir, { recursive: true, force: true }),
13
- ),
14
- );
15
- });
16
-
17
- async function _makeTempDir() {
18
- const dir = await fs.mkdtemp(path.join(os.tmpdir(), "deploy-test-"));
19
- temporaryDirectories.push(dir);
20
- return dir;
21
- }
22
-
23
- async function _writeJson(filePath, value) {
24
- await fs.mkdir(path.dirname(filePath), { recursive: true });
25
- await fs.writeFile(filePath, JSON.stringify(value, null, "\t"));
26
- }
27
-
28
- test("deployToRailway validates CLI prerequisites", async () => {
29
- const { deployToRailway } = await import("./deploy.js");
30
- assert.equal(typeof deployToRailway, "function");
31
- });
32
-
33
- test("inspectProject module loads correctly", async () => {
34
- const { inspectProject } = await import("./inspect.js");
35
- assert.equal(typeof inspectProject, "function");
36
- });
37
-
38
- test("adapters index exports all expected functions", async () => {
39
- const mod = await import("./adapters/index.js");
40
- const expectedExports = [
41
- "checkRailwayCLI",
42
- "checkRailwayLogin",
43
- "deleteService",
44
- "ensureRailwayProject",
45
- "ensurePlugin",
46
- "ensureService",
47
- "deployService",
48
- "getExistingVariable",
49
- "getServiceUrl",
50
- "setProjectVariables",
51
- "getProjectDomains",
52
- "isProjectLinked",
53
- "listProjects",
54
- "tryLinkProject",
55
- "RailwayError",
56
- "RAILWAY_DIAGNOSTIC_CODES",
57
- "generateIacFile",
58
- "applyIacConfig",
59
- "installRailwaySdk",
60
- "escapeTsString",
61
- "getDeploymentStatus",
62
- ];
63
- for (const name of expectedExports) {
64
- assert.ok(name in mod, `Expected export "${name}" to exist`);
65
- }
66
- });
67
-
68
- test("deploy index exports all expected functions", async () => {
69
- const mod = await import("./index.js");
70
- const expectedExports = [
71
- "QUARK_DEPLOY_PROJECT_KIND",
72
- "QUARK_SERVICE_CONTRACTS",
73
- "QUARK_SERVICE_KINDS",
74
- "SUPPORTED_QUARK_SERVICE_KINDS",
75
- "discoverQuarkDeployProject",
76
- "formatQuarkDeployDiagnostics",
77
- "QUARK_DEPLOY_DIAGNOSTIC_CODES",
78
- "resolveQuarkDeployProject",
79
- "deployToRailway",
80
- "inspectProject",
81
- "checkRailwayCLI",
82
- "checkRailwayLogin",
83
- "deleteService",
84
- "ensureRailwayProject",
85
- "ensurePlugin",
86
- "ensureService",
87
- "deployService",
88
- "getExistingVariable",
89
- "getServiceUrl",
90
- "setProjectVariables",
91
- "isProjectLinked",
92
- "listProjects",
93
- "tryLinkProject",
94
- "RailwayError",
95
- "RAILWAY_DIAGNOSTIC_CODES",
96
- "generateIacFile",
97
- "applyIacConfig",
98
- "installRailwaySdk",
99
- "escapeTsString",
100
- "getDeploymentStatus",
101
- ];
102
- for (const name of expectedExports) {
103
- assert.ok(name in mod, `Expected export "${name}" to exist`);
104
- }
105
- });
@@ -1,141 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import fs from "node:fs/promises";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { after, test } from "node:test";
6
- import {
7
- discoverQuarkDeployProject,
8
- QUARK_DEPLOY_DIAGNOSTIC_CODES,
9
- resolveQuarkDeployProject,
10
- } from "./discovery.js";
11
-
12
- const temporaryDirectories = [];
13
-
14
- after(async () => {
15
- await Promise.all(
16
- temporaryDirectories.map((directory) =>
17
- fs.rm(directory, { recursive: true, force: true }),
18
- ),
19
- );
20
- });
21
-
22
- async function writeJson(filePath, value) {
23
- await fs.mkdir(path.dirname(filePath), { recursive: true });
24
- await fs.writeFile(filePath, JSON.stringify(value, null, "\t"));
25
- }
26
-
27
- async function createFixture({ hasWeb = true, hasWorker = false } = {}) {
28
- const projectDir = await fs.mkdtemp(path.join(os.tmpdir(), "quark-deploy-"));
29
- temporaryDirectories.push(projectDir);
30
-
31
- await writeJson(path.join(projectDir, "package.json"), {
32
- name: "fixture-quark-project",
33
- private: true,
34
- });
35
-
36
- if (hasWeb) {
37
- await writeJson(path.join(projectDir, "apps", "web", "package.json"), {
38
- name: "@fixture/quark-web",
39
- private: true,
40
- type: "module",
41
- });
42
- }
43
-
44
- if (hasWorker) {
45
- await writeJson(path.join(projectDir, "apps", "worker", "package.json"), {
46
- name: "@fixture/quark-worker",
47
- private: true,
48
- type: "module",
49
- });
50
- }
51
-
52
- return projectDir;
53
- }
54
-
55
- test("resolveQuarkDeployProject discovers web and worker services in a Quark monorepo", async () => {
56
- const projectDir = await createFixture({ hasWeb: true, hasWorker: true });
57
- const discovery = await resolveQuarkDeployProject(projectDir);
58
-
59
- assert.equal(discovery.kind, "quark");
60
- assert.deepEqual(
61
- discovery.services.map((service) => service.kind),
62
- ["web", "worker"],
63
- );
64
-
65
- const [webService, workerService] = discovery.services;
66
-
67
- assert.equal(webService.required, true);
68
- assert.equal(webService.packageName, "@fixture/quark-web");
69
- assert.equal(webService.relativeRootDir, "apps/web");
70
- assert.equal(
71
- webService.runtime.relativeEntrypoint,
72
- "apps/web/.next/standalone/apps/web/server.js",
73
- );
74
- assert.equal(webService.runtime.healthcheckPath, "/api/health");
75
- assert.equal(
76
- webService.runtime.entrypointPath,
77
- path.join(
78
- projectDir,
79
- "apps",
80
- "web",
81
- ".next",
82
- "standalone",
83
- "apps",
84
- "web",
85
- "server.js",
86
- ),
87
- );
88
-
89
- assert.equal(workerService.required, false);
90
- assert.equal(workerService.packageName, "@fixture/quark-worker");
91
- assert.equal(workerService.relativeRootDir, "apps/worker");
92
- assert.equal(
93
- workerService.runtime.relativeEntrypoint,
94
- "apps/worker/src/index.js",
95
- );
96
- });
97
-
98
- test("resolveQuarkDeployProject accepts a Quark monorepo without a worker service", async () => {
99
- const projectDir = await createFixture({ hasWeb: true, hasWorker: false });
100
- const discovery = await resolveQuarkDeployProject(projectDir);
101
-
102
- assert.equal(discovery.diagnostics.length, 0);
103
- assert.deepEqual(
104
- discovery.services.map((service) => service.kind),
105
- ["web"],
106
- );
107
- assert.equal(discovery.services[0].required, true);
108
- });
109
-
110
- test("discoverQuarkDeployProject reports diagnostics and resolveQuarkDeployProject rejects when web is missing", async () => {
111
- const projectDir = await createFixture({ hasWeb: false, hasWorker: true });
112
- const discovery = await discoverQuarkDeployProject(projectDir);
113
-
114
- assert.deepEqual(
115
- discovery.services.map((service) => service.kind),
116
- ["worker"],
117
- );
118
- assert.deepEqual(discovery.diagnostics, [
119
- {
120
- code: QUARK_DEPLOY_DIAGNOSTIC_CODES.MISSING_REQUIRED_SERVICE,
121
- serviceKind: "web",
122
- required: true,
123
- expectedPath: "apps/web/package.json",
124
- message: "Missing required Quark web service at apps/web/package.json.",
125
- },
126
- ]);
127
-
128
- await assert.rejects(
129
- () => resolveQuarkDeployProject(projectDir),
130
- (error) => {
131
- assert.equal(error.code, "QUARK_DEPLOY_DISCOVERY_FAILED");
132
- assert.equal(error.diagnostics.length, 1);
133
- assert.match(error.message, /apps\/web\/package\.json/);
134
- assert.deepEqual(
135
- error.discovery.services.map((service) => service.kind),
136
- ["worker"],
137
- );
138
- return true;
139
- },
140
- );
141
- });
@@ -1,232 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { readFileSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { test } from "node:test";
5
-
6
- const TEMPLATES_DIR = join(import.meta.dirname, "../templates");
7
-
8
- // ---------------------------------------------------------------------------
9
- // Mobile template structure
10
- // ---------------------------------------------------------------------------
11
-
12
- test("mobile template has required files", () => {
13
- const requiredFiles = [
14
- "app.json",
15
- "package.json",
16
- "tsconfig.json",
17
- "eas.json",
18
- "lib/config.ts",
19
- "lib/auth.ts",
20
- "lib/api.ts",
21
- "lib/api-client.ts",
22
- "lib/notifications.ts",
23
- "lib/storage.ts",
24
- "hooks/use-auth.ts",
25
- "app/_layout.tsx",
26
- "app/(auth)/sign-in.tsx",
27
- "app/(auth)/sign-up.tsx",
28
- "app/(app)/index.tsx",
29
- "app/(app)/profile.tsx",
30
- ];
31
-
32
- for (const file of requiredFiles) {
33
- const path = join(TEMPLATES_DIR, "mobile", file);
34
- assert.ok(
35
- readFileSync(path, "utf-8").length > 0,
36
- `mobile template missing or empty: ${file}`,
37
- );
38
- }
39
- });
40
-
41
- // ---------------------------------------------------------------------------
42
- // Package.json transforms
43
- // ---------------------------------------------------------------------------
44
-
45
- test("mobile package.json uses @myquark scope", () => {
46
- const pkg = JSON.parse(
47
- readFileSync(join(TEMPLATES_DIR, "mobile/package.json"), "utf-8"),
48
- );
49
- assert.strictEqual(pkg.name, "@myquark/mobile");
50
- });
51
-
52
- test("mobile package.json includes expo-constants", () => {
53
- const pkg = JSON.parse(
54
- readFileSync(join(TEMPLATES_DIR, "mobile/package.json"), "utf-8"),
55
- );
56
- assert.ok(
57
- pkg.dependencies?.["expo-constants"],
58
- "mobile package.json missing expo-constants dependency",
59
- );
60
- });
61
-
62
- test("mobile package.json has no monorepo workspace refs in devDependencies", () => {
63
- const pkg = JSON.parse(
64
- readFileSync(join(TEMPLATES_DIR, "mobile/package.json"), "utf-8"),
65
- );
66
- if (pkg.devDependencies) {
67
- for (const dep of Object.keys(pkg.devDependencies)) {
68
- assert.ok(
69
- !dep.startsWith("@usequark/quark-"),
70
- `mobile devDependencies has monorepo ref: ${dep}`,
71
- );
72
- }
73
- }
74
- });
75
-
76
- // ---------------------------------------------------------------------------
77
- // App.json transforms
78
- // ---------------------------------------------------------------------------
79
-
80
- test("mobile app.json has runtimeVersion for OTA updates", () => {
81
- const appJson = JSON.parse(
82
- readFileSync(join(TEMPLATES_DIR, "mobile/app.json"), "utf-8"),
83
- );
84
- assert.deepStrictEqual(appJson.expo.runtimeVersion, {
85
- policy: "appVersion",
86
- });
87
- });
88
-
89
- test("mobile app.json has __QUARK_PROJECT_NAME__ placeholder in slug", () => {
90
- const content = readFileSync(join(TEMPLATES_DIR, "mobile/app.json"), "utf-8");
91
- assert.ok(
92
- content.includes("__QUARK_PROJECT_NAME__"),
93
- "app.json should contain __QUARK_PROJECT_NAME__ placeholder",
94
- );
95
- });
96
-
97
- test("mobile app.json has __QUARK_EAS_PROJECT_ID__ placeholder", () => {
98
- const content = readFileSync(join(TEMPLATES_DIR, "mobile/app.json"), "utf-8");
99
- assert.ok(
100
- content.includes("__QUARK_EAS_PROJECT_ID__"),
101
- "app.json should contain __QUARK_EAS_PROJECT_ID__ placeholder",
102
- );
103
- });
104
-
105
- // ---------------------------------------------------------------------------
106
- // Source code uses lazy config (not module-scope throw)
107
- // ---------------------------------------------------------------------------
108
-
109
- test("mobile config.ts uses lazy getConfig() pattern", () => {
110
- const content = readFileSync(
111
- join(TEMPLATES_DIR, "mobile/lib/config.ts"),
112
- "utf-8",
113
- );
114
- // Should export a getConfig function (lazy — not a module-scope constant)
115
- assert.ok(
116
- content.includes("export function getConfig"),
117
- "config.ts should export getConfig()",
118
- );
119
- // Should NOT have a top-level API_URL constant (the old broken pattern)
120
- assert.ok(
121
- !content.includes("const API_URL"),
122
- "config.ts should not use top-level const API_URL — use lazy getConfig()",
123
- );
124
- });
125
-
126
- // ---------------------------------------------------------------------------
127
- // Notifications use dynamic imports and shouldShowBanner
128
- // ---------------------------------------------------------------------------
129
-
130
- test("mobile notifications.ts uses dynamic imports", () => {
131
- const content = readFileSync(
132
- join(TEMPLATES_DIR, "mobile/lib/notifications.ts"),
133
- "utf-8",
134
- );
135
- assert.ok(
136
- content.includes('await import("expo-notifications")'),
137
- "notifications.ts should use dynamic import for expo-notifications",
138
- );
139
- assert.ok(
140
- content.includes('await import("expo-device")'),
141
- "notifications.ts should use dynamic import for expo-device",
142
- );
143
- });
144
-
145
- test("mobile notifications.ts uses shouldShowBanner/shouldShowList", () => {
146
- const content = readFileSync(
147
- join(TEMPLATES_DIR, "mobile/lib/notifications.ts"),
148
- "utf-8",
149
- );
150
- assert.ok(
151
- content.includes("shouldShowBanner"),
152
- "notifications.ts should use shouldShowBanner",
153
- );
154
- assert.ok(
155
- content.includes("shouldShowList"),
156
- "notifications.ts should use shouldShowList",
157
- );
158
- assert.ok(
159
- !content.includes("shouldShowAlert"),
160
- "notifications.ts should not use deprecated shouldShowAlert",
161
- );
162
- });
163
-
164
- // ---------------------------------------------------------------------------
165
- // Auth screens use TS-strict-safe error handling
166
- // ---------------------------------------------------------------------------
167
-
168
- test("mobile sign-in.tsx uses safe error handling", () => {
169
- const content = readFileSync(
170
- join(TEMPLATES_DIR, "mobile/app/(auth)/sign-in.tsx"),
171
- "utf-8",
172
- );
173
- assert.ok(
174
- content.includes("error instanceof Error"),
175
- "sign-in.tsx should use instanceof check for error handling",
176
- );
177
- });
178
-
179
- test("mobile sign-up.tsx uses safe error handling", () => {
180
- const content = readFileSync(
181
- join(TEMPLATES_DIR, "mobile/app/(auth)/sign-up.tsx"),
182
- "utf-8",
183
- );
184
- assert.ok(
185
- content.includes("error instanceof Error"),
186
- "sign-up.tsx should use instanceof check for error handling",
187
- );
188
- });
189
-
190
- // ---------------------------------------------------------------------------
191
- // use-auth uses lazy notification imports
192
- // ---------------------------------------------------------------------------
193
-
194
- test("mobile use-auth.ts uses lazy notification imports", () => {
195
- const content = readFileSync(
196
- join(TEMPLATES_DIR, "mobile/hooks/use-auth.ts"),
197
- "utf-8",
198
- );
199
- assert.ok(
200
- content.includes('import("../lib/notifications")'),
201
- "use-auth.ts should use dynamic import for notifications",
202
- );
203
- });
204
-
205
- // ---------------------------------------------------------------------------
206
- // No placeholder leaks — all __QUARK_ placeholders are in expected files
207
- // ---------------------------------------------------------------------------
208
-
209
- test("mobile template has no stray __QUARK_ placeholders in source files", () => {
210
- const sourceFiles = [
211
- "lib/config.ts",
212
- "lib/auth.ts",
213
- "lib/api.ts",
214
- "lib/api-client.ts",
215
- "lib/notifications.ts",
216
- "lib/storage.ts",
217
- "hooks/use-auth.ts",
218
- "app/_layout.tsx",
219
- "app/(auth)/sign-in.tsx",
220
- "app/(auth)/sign-up.tsx",
221
- "app/(app)/index.tsx",
222
- "app/(app)/profile.tsx",
223
- ];
224
-
225
- for (const file of sourceFiles) {
226
- const content = readFileSync(join(TEMPLATES_DIR, "mobile", file), "utf-8");
227
- assert.ok(
228
- !content.includes("__QUARK_"),
229
- `${file} contains unreplaced __QUARK_ placeholder`,
230
- );
231
- }
232
- });