anhedral 0.10.0 → 0.11.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/README.md +78 -39
- package/assets/{anhedral-cli-init-technical.svg → anhedral-stack-technical.svg} +6 -6
- package/assets/{anhedral-cli-init.svg → anhedral-stack.svg} +7 -7
- package/assets/security/audit-deps.mjs +6 -1
- package/dist/bin.js +271 -154
- package/dist/bounded-file.d.ts +1 -0
- package/dist/bounded-file.js +1 -0
- package/dist/capabilities.d.ts +4 -4
- package/dist/capabilities.js +1 -1
- package/dist/capability-contract.d.ts +47 -0
- package/dist/capability-contract.js +136 -0
- package/dist/cli-arguments.d.ts +16 -0
- package/dist/cli-arguments.js +115 -0
- package/dist/cli-output.d.ts +8 -0
- package/dist/cli-output.js +211 -0
- package/dist/cloudflare.d.ts +7 -2
- package/dist/cloudflare.js +166 -36
- package/dist/command-schema.d.ts +21 -0
- package/dist/command-schema.js +374 -0
- package/dist/delivery.d.ts +2 -2
- package/dist/delivery.js +21 -4
- package/dist/dependencies.d.ts +32 -0
- package/dist/dependencies.js +47 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +15 -1
- package/dist/inspector-model.d.ts +115 -0
- package/dist/inspector-model.js +1 -0
- package/dist/lifecycle-model.d.ts +15 -0
- package/dist/lifecycle-model.js +20 -0
- package/dist/lifecycle.d.ts +12 -0
- package/dist/lifecycle.js +92 -0
- package/dist/mobile-delivery.d.ts +100 -0
- package/dist/mobile-delivery.js +319 -0
- package/dist/platforms/desktop.js +4 -5
- package/dist/platforms/extension.js +9 -73
- package/dist/platforms/mobile.js +14 -52
- package/dist/project-inspector.d.ts +4 -0
- package/dist/project-inspector.js +646 -0
- package/dist/project-state.d.ts +17 -0
- package/dist/project-state.js +239 -11
- package/dist/provider-auth.d.ts +41 -0
- package/dist/provider-auth.js +210 -0
- package/dist/providers/adapters.d.ts +17 -0
- package/dist/providers/adapters.js +269 -0
- package/dist/providers/api.d.ts +28 -0
- package/dist/providers/api.js +252 -0
- package/dist/providers/config.d.ts +15 -0
- package/dist/providers/config.js +184 -0
- package/dist/providers/definitions.d.ts +318 -0
- package/dist/providers/definitions.js +127 -0
- package/dist/providers/provision.d.ts +24 -0
- package/dist/providers/provision.js +76 -0
- package/dist/providers/receipts.d.ts +20 -0
- package/dist/providers/receipts.js +66 -0
- package/dist/providers/recovery-verification.d.ts +7 -0
- package/dist/providers/recovery-verification.js +112 -0
- package/dist/providers/recovery.d.ts +30 -0
- package/dist/providers/recovery.js +34 -0
- package/dist/providers/runtime.d.ts +10 -0
- package/dist/providers/runtime.js +231 -0
- package/dist/providers/schema.d.ts +28 -0
- package/dist/providers/schema.js +49 -0
- package/dist/providers/state.d.ts +56 -0
- package/dist/providers/state.js +329 -0
- package/dist/provision.d.ts +1 -1
- package/dist/provision.js +61 -3
- package/dist/readiness.js +11 -3
- package/dist/registry/add.d.ts +6 -0
- package/dist/registry/add.js +35 -4
- package/dist/registry/commands.d.ts +3 -2
- package/dist/registry/commands.js +53 -14
- package/dist/registry/options.js +3 -2
- package/dist/registry/preview.d.ts +11 -0
- package/dist/registry/preview.js +91 -0
- package/dist/registry/receipt.js +1 -0
- package/dist/registry/resolve.js +19 -13
- package/dist/registry/types.d.ts +1 -0
- package/dist/registry/usage.d.ts +10 -0
- package/dist/registry/usage.js +22 -37
- package/dist/render.d.ts +2 -0
- package/dist/render.js +10 -0
- package/dist/scaffold/api.js +20 -3
- package/dist/scaffold/auth.js +68 -28
- package/dist/scaffold/bootstrap.d.ts +2 -0
- package/dist/scaffold/bootstrap.js +9 -0
- package/dist/scaffold/database.js +12 -6
- package/dist/scaffold/documentation.js +11 -8
- package/dist/scaffold/environment.d.ts +2 -0
- package/dist/scaffold/environment.js +18 -1
- package/dist/scaffold/files.d.ts +1 -0
- package/dist/scaffold/files.js +15 -4
- package/dist/scaffold/services.js +37 -13
- package/dist/scaffold/stack-documentation.d.ts +3 -0
- package/dist/scaffold/stack-documentation.js +52 -0
- package/dist/scaffold/styling.d.ts +3 -0
- package/dist/scaffold/styling.js +84 -0
- package/dist/scaffold/web.js +31 -11
- package/dist/scaffold/workspace.js +49 -22
- package/dist/ship.d.ts +320 -0
- package/dist/ship.js +211 -0
- package/dist/stack-registry.d.ts +2 -2
- package/dist/stack-registry.js +44 -11
- package/dist/standard.d.ts +2 -1
- package/dist/standard.js +13 -12
- package/dist/vercel.d.ts +3 -3
- package/dist/vercel.js +27 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/vinext.js +9 -18
- package/dist/workspace-graph.js +3 -2
- package/docs/application-stack-standard.md +51 -80
- package/docs/cli-design.md +67 -0
- package/docs/delivery-workflow.md +29 -15
- package/docs/plugin-ui.md +61 -0
- package/docs/provider-configuration.md +117 -0
- package/package.json +16 -12
- package/dist/managed-hosting.d.ts +0 -49
- package/dist/managed-hosting.js +0 -373
- package/docs/managed-hosting-architecture.md +0 -87
- package/docs/sponsorships.md +0 -25
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { realpathSync, existsSync, writeFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { EAS_CLI_VERSION } from './dependencies.js';
|
|
6
|
+
import { readBoundedFile, MAX_PROJECT_JSON_BYTES } from './bounded-file.js';
|
|
7
|
+
import { requiredProviders } from './provider-auth.js';
|
|
8
|
+
import { planProviderProvisioning, provisionSelectedProviders } from './providers/provision.js';
|
|
9
|
+
import { configuredRuntime, runtimeTargets, RUNTIME_KEYS } from './providers/runtime.js';
|
|
10
|
+
import { CREDENTIALS } from './providers/api.js';
|
|
11
|
+
import { atomicJson, defaultRunner } from './project-state.js';
|
|
12
|
+
import { withLifecycleOperation } from './lifecycle.js';
|
|
13
|
+
function artifactUrl(value) {
|
|
14
|
+
if (typeof value !== 'string')
|
|
15
|
+
return undefined;
|
|
16
|
+
try {
|
|
17
|
+
const url = new URL(value);
|
|
18
|
+
if (url.protocol !== 'https:' || url.username || url.password)
|
|
19
|
+
return undefined;
|
|
20
|
+
return url.href;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function pendingBuild(root, commit, options, projectId) {
|
|
27
|
+
const file = path.join(root, '.anhedral/mobile-build.pending.json');
|
|
28
|
+
if (!existsSync(file))
|
|
29
|
+
return undefined;
|
|
30
|
+
let pending;
|
|
31
|
+
try {
|
|
32
|
+
pending = JSON.parse(readBoundedFile(file, MAX_PROJECT_JSON_BYTES).toString('utf8'));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw new Error('Invalid pending EAS build. Inspect the selected project before retrying.');
|
|
36
|
+
}
|
|
37
|
+
if (!pending ||
|
|
38
|
+
typeof pending !== 'object' ||
|
|
39
|
+
Array.isArray(pending) ||
|
|
40
|
+
pending.schemaVersion !== 1 ||
|
|
41
|
+
Object.keys(pending).some((key) => ![
|
|
42
|
+
'schemaVersion',
|
|
43
|
+
'status',
|
|
44
|
+
'commit',
|
|
45
|
+
'platform',
|
|
46
|
+
'profile',
|
|
47
|
+
'projectId',
|
|
48
|
+
'buildId',
|
|
49
|
+
].includes(key)) ||
|
|
50
|
+
![undefined, 'submitting', 'queued'].includes(pending.status) ||
|
|
51
|
+
pending.commit !== commit ||
|
|
52
|
+
pending.platform !== options.platform ||
|
|
53
|
+
pending.profile !== options.profile ||
|
|
54
|
+
pending.projectId !== projectId ||
|
|
55
|
+
(pending.status === 'submitting'
|
|
56
|
+
? Object.hasOwn(pending, 'buildId')
|
|
57
|
+
: typeof pending.buildId !== 'string' || !/^[a-zA-Z0-9-]{1,255}$/.test(pending.buildId)))
|
|
58
|
+
throw new Error('Pending EAS build differs from the selected source or profile.');
|
|
59
|
+
if (pending.status === 'submitting')
|
|
60
|
+
throw new Error('EAS submission has an unknown outcome. Reconcile the selected project before retrying.');
|
|
61
|
+
return pending;
|
|
62
|
+
}
|
|
63
|
+
/** EAS builds produce artifacts; store submission is a separate user action. */
|
|
64
|
+
export async function buildMobileApplication(root, options, run = defaultRunner) {
|
|
65
|
+
if (options.dryRun)
|
|
66
|
+
return executeMobileBuild(root, options, run);
|
|
67
|
+
// Validate local build configuration before creating an execution record.
|
|
68
|
+
await executeMobileBuild(root, { ...options, dryRun: true }, run);
|
|
69
|
+
return withLifecycleOperation(root, 'mobile', run, (session) => session.phase('mobile-build', () => executeMobileBuild(root, options, run)));
|
|
70
|
+
}
|
|
71
|
+
async function executeMobileBuild(root, options, run = defaultRunner) {
|
|
72
|
+
root = realpathSync(root);
|
|
73
|
+
if (!['ios', 'android'].includes(options.platform) ||
|
|
74
|
+
!/^[a-z][a-z0-9-]{0,63}$/.test(options.profile))
|
|
75
|
+
throw new Error('Select an explicit mobile platform and build profile.');
|
|
76
|
+
if (!requiredProviders(root).some((provider) => provider.id === 'expo'))
|
|
77
|
+
throw new Error('This project does not select Expo.');
|
|
78
|
+
const directory = realpathSync(path.join(root, 'apps/mobile'));
|
|
79
|
+
if (!directory.startsWith(root + path.sep))
|
|
80
|
+
throw new Error('Mobile app escapes project directory.');
|
|
81
|
+
let configuration;
|
|
82
|
+
try {
|
|
83
|
+
configuration = JSON.parse(readBoundedFile(path.join(directory, 'eas.json'), MAX_PROJECT_JSON_BYTES).toString('utf8'));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
throw new Error('A valid EAS build configuration is required.');
|
|
87
|
+
}
|
|
88
|
+
const profile = configuration?.build?.[options.profile];
|
|
89
|
+
if (!profile || typeof profile !== 'object' || Array.isArray(profile))
|
|
90
|
+
throw new Error('Requested EAS profile is not configured.');
|
|
91
|
+
if (profile.extends)
|
|
92
|
+
throw new Error('Resolve inherited EAS profiles before automatic delivery.');
|
|
93
|
+
const selectedEnvironment = profile.environment ??
|
|
94
|
+
(profile.developmentClient
|
|
95
|
+
? 'development'
|
|
96
|
+
: profile.distribution === 'internal'
|
|
97
|
+
? 'preview'
|
|
98
|
+
: 'production');
|
|
99
|
+
if (!['development', 'preview', 'production'].includes(selectedEnvironment))
|
|
100
|
+
throw new Error('Select an explicit supported EAS environment.');
|
|
101
|
+
const args = [
|
|
102
|
+
'dlx',
|
|
103
|
+
`eas-cli@${EAS_CLI_VERSION}`,
|
|
104
|
+
'build',
|
|
105
|
+
'--platform',
|
|
106
|
+
options.platform,
|
|
107
|
+
'--profile',
|
|
108
|
+
options.profile,
|
|
109
|
+
'--non-interactive',
|
|
110
|
+
'--json',
|
|
111
|
+
'--no-wait',
|
|
112
|
+
];
|
|
113
|
+
if (options.dryRun)
|
|
114
|
+
return {
|
|
115
|
+
platform: options.platform,
|
|
116
|
+
profile: options.profile,
|
|
117
|
+
command: ['pnpm', ...args],
|
|
118
|
+
mutation: false,
|
|
119
|
+
storeSubmission: false,
|
|
120
|
+
integrations: planProviderProvisioning(root),
|
|
121
|
+
};
|
|
122
|
+
const app = JSON.parse(readBoundedFile(path.join(directory, 'app.json'), MAX_PROJECT_JSON_BYTES).toString('utf8')).expo;
|
|
123
|
+
if (['app.config.js', 'app.config.ts'].some((file) => existsSync(path.join(directory, file))))
|
|
124
|
+
throw new Error('Automatic EAS setup requires a static app.json with explicit owner and slug.');
|
|
125
|
+
if (typeof app?.owner !== 'string' ||
|
|
126
|
+
!/^[a-zA-Z0-9_-]+$/.test(app.owner) ||
|
|
127
|
+
typeof app.slug !== 'string' ||
|
|
128
|
+
!/^[a-zA-Z0-9_-]+$/.test(app.slug))
|
|
129
|
+
throw new Error('Set the intended Expo owner and slug in app.json before shipping.');
|
|
130
|
+
const environment = { ...process.env, CI: '1', NO_COLOR: '1' };
|
|
131
|
+
// Expo keeps its own login credential; server/provider keys never reach native builds.
|
|
132
|
+
for (const key of [...Object.values(CREDENTIALS), ...RUNTIME_KEYS])
|
|
133
|
+
if (!key.startsWith('EXPO_PUBLIC_'))
|
|
134
|
+
delete environment[key];
|
|
135
|
+
const source = async () => {
|
|
136
|
+
if (realpathSync((await run('git', ['rev-parse', '--show-toplevel'], root, environment)).trim()) !== root ||
|
|
137
|
+
(await run('git', ['status', '--porcelain'], root, environment)).trim())
|
|
138
|
+
throw new Error('Mobile delivery requires a clean application Git repository.');
|
|
139
|
+
const commit = (await run('git', ['rev-parse', 'HEAD'], root, environment)).trim();
|
|
140
|
+
if (!/^[a-f0-9]{40,64}$/.test(commit))
|
|
141
|
+
throw new Error('Invalid mobile source commit.');
|
|
142
|
+
return commit;
|
|
143
|
+
};
|
|
144
|
+
const initialCommit = await source();
|
|
145
|
+
// An unresolved queue mutation blocks every subsequent provisioning/configuration mutation.
|
|
146
|
+
pendingBuild(root, initialCommit, options, app.extra?.eas?.projectId);
|
|
147
|
+
await run('pnpm', ['run', 'check'], root, environment);
|
|
148
|
+
if ((await source()) !== initialCommit)
|
|
149
|
+
throw new Error('Source changed during mobile checks.');
|
|
150
|
+
const configured = await provisionSelectedProviders(root, false, run);
|
|
151
|
+
if (!configured.ready)
|
|
152
|
+
return {
|
|
153
|
+
state: 'provider-configuration-required',
|
|
154
|
+
deployed: false,
|
|
155
|
+
integrations: configured,
|
|
156
|
+
storeSubmission: false,
|
|
157
|
+
};
|
|
158
|
+
const runtime = configuredRuntime(root);
|
|
159
|
+
const commit = await source();
|
|
160
|
+
if (commit !== initialCommit)
|
|
161
|
+
throw new Error('Source changed during provider configuration.');
|
|
162
|
+
const cli = ['dlx', `eas-cli@${EAS_CLI_VERSION}`];
|
|
163
|
+
if (!app.extra?.eas?.projectId) {
|
|
164
|
+
await run('pnpm', [...cli, 'project:init', '--non-interactive', '--force'], directory, environment);
|
|
165
|
+
const linked = JSON.parse(readBoundedFile(path.join(directory, 'app.json'), MAX_PROJECT_JSON_BYTES).toString('utf8')).expo;
|
|
166
|
+
if (linked?.owner !== app.owner ||
|
|
167
|
+
linked.slug !== app.slug ||
|
|
168
|
+
typeof linked.extra?.eas?.projectId !== 'string' ||
|
|
169
|
+
!/^[a-f0-9-]{36}$/i.test(linked.extra.eas.projectId))
|
|
170
|
+
throw new Error('EAS did not confirm a project link for the selected owner and slug. Inspect the account before retrying.');
|
|
171
|
+
return {
|
|
172
|
+
state: 'configuration-required',
|
|
173
|
+
deployed: false,
|
|
174
|
+
storeSubmission: false,
|
|
175
|
+
nextAction: 'Review and commit the EAS project link written to app.json, then run ship again.',
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const projectId = app.extra.eas.projectId;
|
|
179
|
+
if (typeof projectId !== 'string' || !/^[a-f0-9-]{36}$/i.test(projectId))
|
|
180
|
+
throw new Error('Invalid EAS project ID.');
|
|
181
|
+
const remote = await run('pnpm', [...cli, 'project:info'], directory, environment);
|
|
182
|
+
const remoteName = remote
|
|
183
|
+
.split(/\r?\n/)
|
|
184
|
+
.find((line) => /^fullName\s+/.test(line.trim()))
|
|
185
|
+
?.trim()
|
|
186
|
+
.split(/\s+/)[1];
|
|
187
|
+
const remoteId = remote
|
|
188
|
+
.split(/\r?\n/)
|
|
189
|
+
.find((line) => /^ID\s+/.test(line.trim()))
|
|
190
|
+
?.trim()
|
|
191
|
+
.split(/\s+/)[1];
|
|
192
|
+
if (remoteId !== projectId || remoteName !== `@${app.owner}/${app.slug}`)
|
|
193
|
+
throw new Error('EAS project identity differs from the selected owner and project.');
|
|
194
|
+
const values = runtimeTargets(root, runtime)['apps/mobile/.env.local'];
|
|
195
|
+
if (values && Object.keys(values).length) {
|
|
196
|
+
const file = path.join(tmpdir(), `anhedral-expo-${randomUUID()}.env`);
|
|
197
|
+
try {
|
|
198
|
+
writeFileSync(file, Object.entries(values)
|
|
199
|
+
.map(([key, value]) => `${key}=${JSON.stringify(value)}\n`)
|
|
200
|
+
.join(''), { mode: 0o600, flag: 'wx' });
|
|
201
|
+
await run('pnpm', [...cli, 'env:push', '--environment', selectedEnvironment, '--path', file, '--force'], directory, environment);
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
rmSync(file, { force: true });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
let builds;
|
|
208
|
+
let expectedBuildId;
|
|
209
|
+
const pendingFile = path.join(root, '.anhedral/mobile-build.pending.json');
|
|
210
|
+
try {
|
|
211
|
+
const pending = pendingBuild(root, commit, options, projectId);
|
|
212
|
+
if (pending) {
|
|
213
|
+
const buildId = pending.buildId;
|
|
214
|
+
if (!buildId)
|
|
215
|
+
throw new Error('Pending EAS build has no verified ID.');
|
|
216
|
+
expectedBuildId = buildId;
|
|
217
|
+
builds = [
|
|
218
|
+
JSON.parse(await run('pnpm', [...cli, 'build:view', buildId, '--json'], directory, environment)),
|
|
219
|
+
];
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
if ((await source()) !== commit)
|
|
223
|
+
throw new Error('Source changed before EAS build submission.');
|
|
224
|
+
// Record intent before the remote queue mutation, including lost or invalid responses.
|
|
225
|
+
atomicJson(pendingFile, {
|
|
226
|
+
schemaVersion: 1,
|
|
227
|
+
status: 'submitting',
|
|
228
|
+
commit,
|
|
229
|
+
platform: options.platform,
|
|
230
|
+
profile: options.profile,
|
|
231
|
+
projectId,
|
|
232
|
+
});
|
|
233
|
+
builds = JSON.parse(await run('pnpm', args, directory, { ...environment, ...values }));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
throw new Error('EAS build did not return verified completion; inspect the selected Expo project before retrying.');
|
|
238
|
+
}
|
|
239
|
+
if (!Array.isArray(builds) || builds.length !== 1)
|
|
240
|
+
throw new Error('EAS returned an unexpected build selection.');
|
|
241
|
+
const build = builds[0];
|
|
242
|
+
const downloadUrl = artifactUrl(build?.artifacts?.applicationArchiveUrl ?? build?.artifacts?.buildUrl);
|
|
243
|
+
if (typeof build?.id !== 'string' || !/^[a-zA-Z0-9-]{1,255}$/.test(build.id))
|
|
244
|
+
throw new Error('EAS did not return a valid build ID.');
|
|
245
|
+
if (build.project?.id !== projectId ||
|
|
246
|
+
build.project?.ownerAccount?.name !== app.owner ||
|
|
247
|
+
build.project?.slug !== app.slug)
|
|
248
|
+
throw new Error('EAS build belongs to a different project.');
|
|
249
|
+
if (build.platform !== options.platform.toUpperCase() ||
|
|
250
|
+
build.buildProfile !== options.profile ||
|
|
251
|
+
(build.environment !== undefined && build.environment !== selectedEnvironment) ||
|
|
252
|
+
(options.platform === 'ios' && build.isForIosSimulator !== (profile.ios?.simulator === true)))
|
|
253
|
+
throw new Error('EAS build platform, profile or environment differs from the selected configuration.');
|
|
254
|
+
if (build.gitCommitHash !== commit)
|
|
255
|
+
throw new Error('EAS build source differs from the selected source commit.');
|
|
256
|
+
if (expectedBuildId && build.id !== expectedBuildId)
|
|
257
|
+
throw new Error('EAS returned a different build than the recorded selection.');
|
|
258
|
+
atomicJson(pendingFile, {
|
|
259
|
+
schemaVersion: 1,
|
|
260
|
+
status: 'queued',
|
|
261
|
+
commit,
|
|
262
|
+
platform: options.platform,
|
|
263
|
+
profile: options.profile,
|
|
264
|
+
projectId,
|
|
265
|
+
buildId: build.id,
|
|
266
|
+
});
|
|
267
|
+
const buildPage = `https://expo.dev/accounts/${app.owner}/projects/${app.slug}/builds/${build.id}`;
|
|
268
|
+
if (['NEW', 'IN_QUEUE', 'IN_PROGRESS', 'PENDING_CANCEL'].includes(build.status))
|
|
269
|
+
return {
|
|
270
|
+
schemaVersion: 1,
|
|
271
|
+
state: 'building',
|
|
272
|
+
buildId: build.id,
|
|
273
|
+
buildPageUrl: buildPage,
|
|
274
|
+
commit,
|
|
275
|
+
platform: options.platform,
|
|
276
|
+
profile: options.profile,
|
|
277
|
+
storeSubmission: false,
|
|
278
|
+
nextAction: 'Run the same ship command again to inspect this build without creating another build.',
|
|
279
|
+
};
|
|
280
|
+
if (build.status !== 'FINISHED' || !downloadUrl)
|
|
281
|
+
throw new Error('EAS has not confirmed a completed downloadable build. Inspect the recorded build before retrying.');
|
|
282
|
+
if ((await source()) !== commit)
|
|
283
|
+
throw new Error('Source changed during mobile build; do not treat this artifact as the current release.');
|
|
284
|
+
const owner = build.project?.ownerAccount?.name;
|
|
285
|
+
const slug = build.project?.slug;
|
|
286
|
+
const buildPageUrl = typeof owner === 'string' &&
|
|
287
|
+
/^[a-zA-Z0-9_-]+$/.test(owner) &&
|
|
288
|
+
typeof slug === 'string' &&
|
|
289
|
+
/^[a-zA-Z0-9_-]+$/.test(slug)
|
|
290
|
+
? `https://expo.dev/accounts/${owner}/projects/${slug}/builds/${build.id}`
|
|
291
|
+
: undefined;
|
|
292
|
+
const result = {
|
|
293
|
+
schemaVersion: 1,
|
|
294
|
+
projectId,
|
|
295
|
+
owner: app.owner,
|
|
296
|
+
slug: app.slug,
|
|
297
|
+
recordedAt: new Date().toISOString(),
|
|
298
|
+
...(buildPageUrl ? { buildPageUrl } : {}),
|
|
299
|
+
commit,
|
|
300
|
+
platform: options.platform,
|
|
301
|
+
profile: options.profile,
|
|
302
|
+
buildId: build.id,
|
|
303
|
+
downloadUrl,
|
|
304
|
+
state: 'built',
|
|
305
|
+
storeSubmission: false,
|
|
306
|
+
artifact: options.platform === 'ios'
|
|
307
|
+
? profile.ios?.simulator === true
|
|
308
|
+
? 'ios-simulator'
|
|
309
|
+
: 'ipa'
|
|
310
|
+
: profile.android?.buildType === 'apk' ||
|
|
311
|
+
profile.distribution === 'internal' ||
|
|
312
|
+
profile.developmentClient === true
|
|
313
|
+
? 'apk'
|
|
314
|
+
: 'aab',
|
|
315
|
+
};
|
|
316
|
+
atomicJson(path.join(root, '.anhedral/mobile-build.json'), result);
|
|
317
|
+
rmSync(pendingFile, { force: true });
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
@@ -107,9 +107,6 @@ export default defineConfig({
|
|
|
107
107
|
build: {
|
|
108
108
|
outDir: 'dist/renderer',
|
|
109
109
|
emptyOutDir: true,
|
|
110
|
-
// Clerk is already loaded behind a dynamic import. Its optional account-management
|
|
111
|
-
// surface is intentionally a large async chunk and does not inflate initial rendering.
|
|
112
|
-
chunkSizeWarningLimit: 1600,
|
|
113
110
|
},
|
|
114
111
|
});
|
|
115
112
|
`);
|
|
@@ -261,7 +258,7 @@ function isAllowedAppNavigation(value: string): boolean {
|
|
|
261
258
|
}
|
|
262
259
|
|
|
263
260
|
// Product-owned window options and navigation policy belong in this file.
|
|
264
|
-
//
|
|
261
|
+
// main.ts imports this seam and manages application lifecycle.
|
|
265
262
|
export function createAppWindow(): BrowserWindow {
|
|
266
263
|
const window = new BrowserWindow({
|
|
267
264
|
width: 1180,
|
|
@@ -306,11 +303,13 @@ export function createAppWindow(): BrowserWindow {
|
|
|
306
303
|
writeFile(path.join(dir, 'src/main/main.ts'), `import { app, BrowserWindow, session } from 'electron';
|
|
307
304
|
import { createAppWindow } from './app-window.js';
|
|
308
305
|
|
|
309
|
-
|
|
310
306
|
app.whenReady().then(() => {
|
|
311
307
|
session.defaultSession.setPermissionCheckHandler(() => false);
|
|
312
308
|
session.defaultSession.setPermissionRequestHandler((_webContents, _permission, callback) => callback(false));
|
|
313
309
|
createAppWindow();
|
|
310
|
+
}).catch((error) => {
|
|
311
|
+
console.error('Unable to start the desktop app:', error);
|
|
312
|
+
app.quit();
|
|
314
313
|
});
|
|
315
314
|
app.on('window-all-closed', () => {
|
|
316
315
|
if (process.platform !== 'darwin') app.quit();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NATIVE_ESLINT_VERSION } from '../dependencies.js';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import { writeReactLintConfig } from './lint.js';
|
|
3
4
|
import { writeFile } from '../util.js';
|
|
@@ -41,11 +42,14 @@ function writePackageJson(dir, projectName) {
|
|
|
41
42
|
typecheck: 'tsc --noEmit',
|
|
42
43
|
lint: 'eslint src --max-warnings 0',
|
|
43
44
|
},
|
|
44
|
-
dependencies:
|
|
45
|
+
dependencies: {
|
|
46
|
+
...EXTENSION_DEPENDENCIES.dependencies,
|
|
47
|
+
'@workspace/styling': 'workspace:*',
|
|
48
|
+
},
|
|
45
49
|
devDependencies: {
|
|
46
50
|
...EXTENSION_DEPENDENCIES.devDependencies,
|
|
47
51
|
'@workspace/eslint-config': 'workspace:*',
|
|
48
|
-
eslint:
|
|
52
|
+
eslint: NATIVE_ESLINT_VERSION,
|
|
49
53
|
},
|
|
50
54
|
}, null, 2) + '\n');
|
|
51
55
|
}
|
|
@@ -55,13 +59,12 @@ function writeWxtConfig(dir, displayName) {
|
|
|
55
59
|
const actionTitleLiteral = jsString(`Open ${displayName}`);
|
|
56
60
|
writeFile(path.join(dir, 'wxt.config.ts'), `import { defineConfig } from 'wxt';
|
|
57
61
|
|
|
58
|
-
|
|
59
62
|
export default defineConfig({
|
|
60
63
|
srcDir: 'src',
|
|
64
|
+
dev: { server: { port: 3001, strictPort: true } },
|
|
61
65
|
manifest: () => {
|
|
62
66
|
const crxPublicKey = import.meta.env.VITE_CRX_PUBLIC_KEY || '';
|
|
63
67
|
|
|
64
|
-
|
|
65
68
|
return {
|
|
66
69
|
name: ${nameLiteral},
|
|
67
70
|
description: ${descriptionLiteral},
|
|
@@ -176,51 +179,7 @@ function writeTailwindConfig(dir) {
|
|
|
176
179
|
writeFile(path.join(dir, 'tailwind.config.cjs'), `/** @type {import('tailwindcss').Config} */
|
|
177
180
|
module.exports = {
|
|
178
181
|
content: ['./src/**/*.{ts,tsx,html}'],
|
|
179
|
-
|
|
180
|
-
extend: {
|
|
181
|
-
colors: {
|
|
182
|
-
border: 'hsl(var(--border))',
|
|
183
|
-
input: 'hsl(var(--input))',
|
|
184
|
-
ring: 'hsl(var(--ring))',
|
|
185
|
-
background: 'hsl(var(--background))',
|
|
186
|
-
foreground: 'hsl(var(--foreground))',
|
|
187
|
-
primary: {
|
|
188
|
-
DEFAULT: 'hsl(var(--primary))',
|
|
189
|
-
foreground: 'hsl(var(--primary-foreground))',
|
|
190
|
-
},
|
|
191
|
-
secondary: {
|
|
192
|
-
DEFAULT: 'hsl(var(--secondary))',
|
|
193
|
-
foreground: 'hsl(var(--secondary-foreground))',
|
|
194
|
-
},
|
|
195
|
-
destructive: {
|
|
196
|
-
DEFAULT: 'hsl(var(--destructive))',
|
|
197
|
-
foreground: 'hsl(var(--destructive-foreground))',
|
|
198
|
-
},
|
|
199
|
-
muted: {
|
|
200
|
-
DEFAULT: 'hsl(var(--muted))',
|
|
201
|
-
foreground: 'hsl(var(--muted-foreground))',
|
|
202
|
-
},
|
|
203
|
-
accent: {
|
|
204
|
-
DEFAULT: 'hsl(var(--accent))',
|
|
205
|
-
foreground: 'hsl(var(--accent-foreground))',
|
|
206
|
-
},
|
|
207
|
-
popover: {
|
|
208
|
-
DEFAULT: 'hsl(var(--popover))',
|
|
209
|
-
foreground: 'hsl(var(--popover-foreground))',
|
|
210
|
-
},
|
|
211
|
-
card: {
|
|
212
|
-
DEFAULT: 'hsl(var(--card))',
|
|
213
|
-
foreground: 'hsl(var(--card-foreground))',
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
|
-
borderRadius: {
|
|
217
|
-
lg: 'var(--radius)',
|
|
218
|
-
md: 'calc(var(--radius) - 2px)',
|
|
219
|
-
sm: 'calc(var(--radius) - 4px)',
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
plugins: [],
|
|
182
|
+
presets: [require('@workspace/styling/tailwind')],
|
|
224
183
|
};
|
|
225
184
|
`);
|
|
226
185
|
}
|
|
@@ -259,7 +218,7 @@ export default defineBackground(() => {
|
|
|
259
218
|
// Open the side panel when the extension icon is clicked.
|
|
260
219
|
(chrome as ChromeWithSidePanel).sidePanel
|
|
261
220
|
.setPanelBehavior({ openPanelOnActionClick: true })
|
|
262
|
-
.catch(() =>
|
|
221
|
+
.catch((error) => console.error('Unable to configure the side panel:', error));
|
|
263
222
|
});
|
|
264
223
|
`);
|
|
265
224
|
}
|
|
@@ -328,7 +287,6 @@ export function SidePanelApp() {
|
|
|
328
287
|
}
|
|
329
288
|
}, []);
|
|
330
289
|
|
|
331
|
-
|
|
332
290
|
return (
|
|
333
291
|
<div style={{ padding: 24 }}>
|
|
334
292
|
<h2>Welcome!</h2>
|
|
@@ -351,28 +309,6 @@ function writeStyles(dir) {
|
|
|
351
309
|
@tailwind utilities;
|
|
352
310
|
|
|
353
311
|
@layer base {
|
|
354
|
-
:root {
|
|
355
|
-
--background: 0 0% 100%;
|
|
356
|
-
--foreground: 0 0% 9%;
|
|
357
|
-
--card: 0 0% 100%;
|
|
358
|
-
--card-foreground: 0 0% 9%;
|
|
359
|
-
--popover: 0 0% 100%;
|
|
360
|
-
--popover-foreground: 0 0% 9%;
|
|
361
|
-
--primary: 0 0% 9%;
|
|
362
|
-
--primary-foreground: 0 0% 98%;
|
|
363
|
-
--secondary: 0 0% 96.1%;
|
|
364
|
-
--secondary-foreground: 0 0% 9%;
|
|
365
|
-
--muted: 0 0% 96.1%;
|
|
366
|
-
--muted-foreground: 0 0% 45.1%;
|
|
367
|
-
--accent: 0 0% 96.1%;
|
|
368
|
-
--accent-foreground: 0 0% 9%;
|
|
369
|
-
--destructive: 0 84.2% 60.2%;
|
|
370
|
-
--destructive-foreground: 0 0% 98%;
|
|
371
|
-
--border: 0 0% 89.8%;
|
|
372
|
-
--input: 0 0% 89.8%;
|
|
373
|
-
--ring: 0 0% 63.9%;
|
|
374
|
-
--radius: 0.5rem;
|
|
375
|
-
}
|
|
376
312
|
|
|
377
313
|
* {
|
|
378
314
|
border-color: hsl(var(--border));
|
package/dist/platforms/mobile.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NATIVE_ESLINT_VERSION } from '../dependencies.js';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import { writeReactLintConfig } from './lint.js';
|
|
3
4
|
import { appendGitignore, writeFile } from '../util.js';
|
|
@@ -62,26 +63,9 @@ module.exports = withNativeWind(config, { input: './global.css', inlineRem: 16 }
|
|
|
62
63
|
|
|
63
64
|
/** @type {import('tailwindcss').Config} */
|
|
64
65
|
module.exports = {
|
|
65
|
-
darkMode: 'class',
|
|
66
66
|
content: ['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'],
|
|
67
|
-
presets: [require('nativewind/preset')],
|
|
68
|
-
theme: {
|
|
69
|
-
extend: {
|
|
70
|
-
colors: {
|
|
71
|
-
border: 'hsl(var(--border))', input: 'hsl(var(--input))', ring: 'hsl(var(--ring))',
|
|
72
|
-
background: 'hsl(var(--background))', foreground: 'hsl(var(--foreground))',
|
|
73
|
-
primary: { DEFAULT: 'hsl(var(--primary))', foreground: 'hsl(var(--primary-foreground))' },
|
|
74
|
-
secondary: { DEFAULT: 'hsl(var(--secondary))', foreground: 'hsl(var(--secondary-foreground))' },
|
|
75
|
-
destructive: { DEFAULT: 'hsl(var(--destructive))', foreground: 'hsl(var(--destructive-foreground))' },
|
|
76
|
-
muted: { DEFAULT: 'hsl(var(--muted))', foreground: 'hsl(var(--muted-foreground))' },
|
|
77
|
-
accent: { DEFAULT: 'hsl(var(--accent))', foreground: 'hsl(var(--accent-foreground))' },
|
|
78
|
-
popover: { DEFAULT: 'hsl(var(--popover))', foreground: 'hsl(var(--popover-foreground))' },
|
|
79
|
-
card: { DEFAULT: 'hsl(var(--card))', foreground: 'hsl(var(--card-foreground))' },
|
|
80
|
-
},
|
|
81
|
-
borderRadius: { lg: 'var(--radius)', md: 'calc(var(--radius) - 2px)', sm: 'calc(var(--radius) - 4px)' },
|
|
82
|
-
borderWidth: { hairline: hairlineWidth() },
|
|
83
|
-
},
|
|
84
|
-
},
|
|
67
|
+
presets: [require('nativewind/preset'), require('@workspace/styling/tailwind')],
|
|
68
|
+
theme: { extend: { borderWidth: { hairline: hairlineWidth() } } },
|
|
85
69
|
future: { hoverOnlyWhenSupported: true },
|
|
86
70
|
plugins: [require('tailwindcss-animate')],
|
|
87
71
|
};
|
|
@@ -89,31 +73,6 @@ module.exports = {
|
|
|
89
73
|
writeFile(path.join(dir, 'global.css'), `@tailwind base;
|
|
90
74
|
@tailwind components;
|
|
91
75
|
@tailwind utilities;
|
|
92
|
-
|
|
93
|
-
@layer base {
|
|
94
|
-
:root {
|
|
95
|
-
--background: 0 0% 100%; --foreground: 0 0% 3.9%;
|
|
96
|
-
--card: 0 0% 100%; --card-foreground: 0 0% 3.9%;
|
|
97
|
-
--popover: 0 0% 100%; --popover-foreground: 0 0% 3.9%;
|
|
98
|
-
--primary: 0 0% 9%; --primary-foreground: 0 0% 98%;
|
|
99
|
-
--secondary: 0 0% 96.1%; --secondary-foreground: 0 0% 9%;
|
|
100
|
-
--muted: 0 0% 96.1%; --muted-foreground: 0 0% 45.1%;
|
|
101
|
-
--accent: 0 0% 96.1%; --accent-foreground: 0 0% 9%;
|
|
102
|
-
--destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 98%;
|
|
103
|
-
--border: 0 0% 89.8%; --input: 0 0% 89.8%; --ring: 0 0% 63%; --radius: 0.625rem;
|
|
104
|
-
}
|
|
105
|
-
.dark:root {
|
|
106
|
-
--background: 0 0% 3.9%; --foreground: 0 0% 98%;
|
|
107
|
-
--card: 0 0% 3.9%; --card-foreground: 0 0% 98%;
|
|
108
|
-
--popover: 0 0% 3.9%; --popover-foreground: 0 0% 98%;
|
|
109
|
-
--primary: 0 0% 98%; --primary-foreground: 0 0% 9%;
|
|
110
|
-
--secondary: 0 0% 14.9%; --secondary-foreground: 0 0% 98%;
|
|
111
|
-
--muted: 0 0% 14.9%; --muted-foreground: 0 0% 63.9%;
|
|
112
|
-
--accent: 0 0% 14.9%; --accent-foreground: 0 0% 98%;
|
|
113
|
-
--destructive: 0 70.9% 59.4%; --destructive-foreground: 0 0% 98%;
|
|
114
|
-
--border: 0 0% 14.9%; --input: 0 0% 14.9%; --ring: 300 0% 45%;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
76
|
`);
|
|
118
77
|
}
|
|
119
78
|
function expoScheme(projectName) {
|
|
@@ -148,23 +107,23 @@ export async function scaffoldExpo({ root, options }) {
|
|
|
148
107
|
build: 'pnpm typecheck && pnpm build:web',
|
|
149
108
|
'build:web': 'expo export --platform web',
|
|
150
109
|
'eas:login': `pnpm dlx eas-cli@${EAS_CLI_VERSION} login`,
|
|
110
|
+
'build:simulator:ios': `pnpm dlx eas-cli@${EAS_CLI_VERSION} build --platform ios --profile simulator`,
|
|
151
111
|
'build:internal:ios': `pnpm dlx eas-cli@${EAS_CLI_VERSION} build --platform ios --profile preview`,
|
|
152
112
|
'build:internal:android': `pnpm dlx eas-cli@${EAS_CLI_VERSION} build --platform android --profile preview`,
|
|
153
113
|
'build:production:ios': `pnpm dlx eas-cli@${EAS_CLI_VERSION} build --platform ios --profile production`,
|
|
154
114
|
'build:production:android': `pnpm dlx eas-cli@${EAS_CLI_VERSION} build --platform android --profile production`,
|
|
155
|
-
'submit:ios': `pnpm dlx eas-cli@${EAS_CLI_VERSION} submit --platform ios --profile production --latest`,
|
|
156
|
-
'submit:android': `pnpm dlx eas-cli@${EAS_CLI_VERSION} submit --platform android --profile production --latest`,
|
|
157
115
|
typecheck: 'tsc --noEmit',
|
|
158
116
|
lint: 'eslint app components lib --max-warnings 0',
|
|
159
117
|
},
|
|
160
118
|
dependencies: {
|
|
161
119
|
...MOBILE_APP_DEPENDENCIES.dependencies,
|
|
162
120
|
...MOBILE_NATIVEWIND_DEPENDENCIES.dependencies,
|
|
121
|
+
'@workspace/styling': 'workspace:*',
|
|
163
122
|
},
|
|
164
123
|
devDependencies: {
|
|
165
124
|
...MOBILE_APP_DEPENDENCIES.devDependencies,
|
|
166
125
|
'@workspace/eslint-config': 'workspace:*',
|
|
167
|
-
eslint:
|
|
126
|
+
eslint: NATIVE_ESLINT_VERSION,
|
|
168
127
|
},
|
|
169
128
|
}, null, 2) + '\n');
|
|
170
129
|
writeFile(path.join(dir, 'app.json'), JSON.stringify({
|
|
@@ -200,7 +159,6 @@ export async function scaffoldExpo({ root, options }) {
|
|
|
200
159
|
import { PortalHost } from '@rn-primitives/portal';
|
|
201
160
|
import { Stack } from 'expo-router';
|
|
202
161
|
|
|
203
|
-
|
|
204
162
|
export default function RootLayout() {
|
|
205
163
|
return <><Stack /><PortalHost /></>;
|
|
206
164
|
}
|
|
@@ -215,7 +173,7 @@ export default function HomeScreen() {
|
|
|
215
173
|
<StatusBar style="auto" />
|
|
216
174
|
<View style={styles.content}>
|
|
217
175
|
<Text accessibilityRole="header" style={styles.title}>{${nameLiteral}}</Text>
|
|
218
|
-
<Text selectable style={styles.subtitle}>
|
|
176
|
+
<Text selectable style={styles.subtitle}>Expo application scaffold generated by Anhedral.</Text>
|
|
219
177
|
<Button onPress={() => void Linking.openURL('https://docs.expo.dev')}>Learn more</Button>
|
|
220
178
|
</View>
|
|
221
179
|
</ScrollView>
|
|
@@ -235,10 +193,14 @@ const styles = StyleSheet.create({
|
|
|
235
193
|
cli: { version: '>= 16.0.0' },
|
|
236
194
|
build: {
|
|
237
195
|
development: { developmentClient: true, distribution: 'internal' },
|
|
238
|
-
preview: {
|
|
239
|
-
|
|
196
|
+
preview: {
|
|
197
|
+
environment: 'preview',
|
|
198
|
+
distribution: 'internal',
|
|
199
|
+
android: { buildType: 'apk' },
|
|
200
|
+
},
|
|
201
|
+
simulator: { environment: 'preview', ios: { simulator: true } },
|
|
202
|
+
production: { environment: 'production', autoIncrement: true },
|
|
240
203
|
},
|
|
241
|
-
submit: { production: {} },
|
|
242
204
|
}, null, 2) + '\n');
|
|
243
205
|
appendGitignore(dir, ['.env', '.env.*', '!.env.example', '.expo', 'dist', 'node_modules']);
|
|
244
206
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProjectInspection } from './inspector-model.js';
|
|
2
|
+
/** Local read-only inspection. Each section has bounded allowlisted inputs and an
|
|
3
|
+
* independent error state. No secrets, commands, provider calls, or migrations. */
|
|
4
|
+
export declare function inspectApplication(root: string): ProjectInspection;
|