@wtfalch/foundry 0.65.0 → 0.67.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/dist/apps/cloudflare.d.ts +5 -0
- package/dist/apps/deploy.js +33 -1
- package/dist/apps/migrations.d.ts +3 -0
- package/dist/apps/migrations.js +32 -7
- package/dist/catalogue/copy-en.js +5 -0
- package/dist/catalogue/integrations.js +5 -0
- package/dist/entry-rules.d.ts +11 -0
- package/dist/entry-rules.js +70 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.js +5 -2
- package/dist/migrations/0069_release_events.sql +16 -0
- package/dist/modules/analytics.d.ts +8 -0
- package/dist/modules/analytics.js +28 -0
- package/dist/modules/database.js +22 -3
- package/dist/modules/index.js +2 -0
- package/dist/native-build.d.ts +16 -12
- package/dist/native-build.js +16 -12
- package/dist/org.d.ts +9 -1
- package/dist/org.js +4 -1
- package/dist/plan/analytics-boule.d.ts +17 -0
- package/dist/plan/analytics-boule.js +43 -0
- package/dist/plan/analytics.d.ts +58 -0
- package/dist/plan/analytics.js +89 -0
- package/dist/plan/boule.js +2 -0
- package/dist/plan/company-backups.js +2 -0
- package/dist/plan/diff.d.ts +4 -0
- package/dist/plan/diff.js +103 -3
- package/dist/plan/entry-analytics-key.d.ts +35 -0
- package/dist/plan/entry-analytics-key.js +134 -0
- package/dist/plan/executor.d.ts +79 -6
- package/dist/plan/executor.js +379 -19
- package/dist/plan/hostnames.js +4 -0
- package/dist/plan/index.d.ts +2 -0
- package/dist/plan/index.js +1 -0
- package/dist/plan/live.d.ts +1 -1
- package/dist/plan/locked-hostnames.js +3 -0
- package/dist/plan/login-host.d.ts +8 -0
- package/dist/plan/login-host.js +8 -0
- package/dist/plan/org-names.d.ts +3 -3
- package/dist/plan/org-names.js +3 -0
- package/dist/plan/own-limits.d.ts +6 -0
- package/dist/plan/own-limits.js +1 -0
- package/dist/plan/plan-apply.d.ts +12 -0
- package/dist/plan/plan-apply.js +40 -1
- package/dist/plan/platform-diff.d.ts +17 -1
- package/dist/plan/platform-diff.js +40 -2
- package/dist/plan/platform-org.d.ts +6 -0
- package/dist/plan/platform-providers.d.ts +8 -6
- package/dist/plan/platform-providers.js +56 -10
- package/dist/plan/providers-readers.js +16 -1
- package/dist/plan/release-host.d.ts +30 -1
- package/dist/plan/release-host.js +84 -41
- package/dist/plan/release-series-reader.d.ts +26 -0
- package/dist/plan/release-series-reader.js +40 -0
- package/dist/plan/sign-in-archon.d.ts +1 -1
- package/dist/plan/sign-in-archon.js +2 -2
- package/dist/plan/stage-services.d.ts +3 -1
- package/dist/plan/stage-services.js +2 -1
- package/dist/plan/uptime-worker.js +53 -3
- package/dist/plan/worker-app-database.d.ts +12 -1
- package/dist/plan/worker-app-database.js +13 -0
- package/dist/plan/worker-app.d.ts +27 -2
- package/dist/plan/worker-app.js +52 -7
- package/dist/plan/workspace.d.ts +2 -2
- package/dist/plan/workspace.js +3 -3
- package/dist/release/decide.d.ts +2 -0
- package/dist/release/decide.js +67 -10
- package/dist/release/events.d.ts +13 -0
- package/dist/release/events.js +67 -0
- package/dist/release/health.d.ts +5 -0
- package/dist/release/health.js +3 -3
- package/dist/release/run.d.ts +7 -2
- package/dist/release/run.js +8 -0
- package/dist/release/series.d.ts +58 -0
- package/dist/release/series.js +65 -0
- package/dist/release/state.d.ts +58 -1
- package/dist/release/state.js +45 -0
- package/dist/release/types.d.ts +44 -0
- package/dist/slug.d.ts +1 -1
- package/dist/slug.js +5 -2
- package/dist/store/postgres.js +29 -3
- package/dist/store/schema.d.ts +112 -0
- package/dist/store/schema.js +9 -0
- package/dist/worker-app-config.d.ts +22 -1
- package/dist/worker-app-config.js +73 -15
- package/dist/worker-app-migrations.d.ts +6 -2
- package/dist/worker-app-migrations.js +1 -0
- package/package.json +1 -1
|
@@ -51,6 +51,11 @@ export interface AppsCloudflare {
|
|
|
51
51
|
}[]>;
|
|
52
52
|
putWorkerSecret(name: string, secretName: string, text: string): Promise<void>;
|
|
53
53
|
deleteWorkerSecret(name: string, secretName: string): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Sets the script's cron triggers to exactly `crons`; an empty list clears them. Optional until
|
|
56
|
+
* providers' client has the call: a deploy that declares crons stops, in words, without it.
|
|
57
|
+
*/
|
|
58
|
+
setWorkerSchedules?(name: string, crons: string[]): Promise<void>;
|
|
54
59
|
/** The account's `<name>.workers.dev` name, or `undefined` when it has none. */
|
|
55
60
|
getWorkersSubdomain(): Promise<string | undefined>;
|
|
56
61
|
setWorkerScriptSubdomain(name: string, enabled: boolean): Promise<void>;
|
package/dist/apps/deploy.js
CHANGED
|
@@ -401,6 +401,21 @@ async function pruneSecrets(ctx, script, secrets) {
|
|
|
401
401
|
ctx.steps.push(`Removed the old secret ${name}`);
|
|
402
402
|
}
|
|
403
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* A live copy's cron triggers are made to match the declaration: set to the list, or cleared when it
|
|
406
|
+
* is empty. A workspace copy runs no scheduled jobs. A client without the call was refused earlier
|
|
407
|
+
* when there was a list to set.
|
|
408
|
+
*/
|
|
409
|
+
async function syncSchedules(ctx, script, crons, copy) {
|
|
410
|
+
if (copy !== 'live')
|
|
411
|
+
return;
|
|
412
|
+
const set = ctx.cf.setWorkerSchedules;
|
|
413
|
+
if (!set)
|
|
414
|
+
return;
|
|
415
|
+
await ctx.call('upload', 'set the app’s scheduled jobs', () => set.call(ctx.cf, script, [...crons]));
|
|
416
|
+
if (crons.length > 0)
|
|
417
|
+
ctx.steps.push(`Set ${crons.length} scheduled ${crons.length === 1 ? 'job' : 'jobs'}`);
|
|
418
|
+
}
|
|
404
419
|
const containerAppName = (script, className) => `${script}-${className}`.toLowerCase();
|
|
405
420
|
async function waitForRollout(ctx, appId, rolloutId, className) {
|
|
406
421
|
const deadline = Date.now() + (ctx.deps.rolloutTimeoutMs ?? ROLLOUT_TIMEOUT_MS);
|
|
@@ -538,7 +553,14 @@ function prepare(input) {
|
|
|
538
553
|
if (clash)
|
|
539
554
|
throw new Stop('check', `${clash} is both a setting and a secret. Keep only one.`);
|
|
540
555
|
const missing = [];
|
|
556
|
+
const unset = [];
|
|
541
557
|
for (const [name, source] of Object.entries(app.secrets ?? {})) {
|
|
558
|
+
if (source.from === 'heldSecret') {
|
|
559
|
+
// The company's own value: without it the app runs without the secret, it is not a stop.
|
|
560
|
+
if (!input.secrets[name])
|
|
561
|
+
unset.push(name);
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
542
564
|
if (!input.secrets[name])
|
|
543
565
|
missing.push(name);
|
|
544
566
|
if (source.from === 'keysCustody' &&
|
|
@@ -573,7 +595,10 @@ function prepare(input) {
|
|
|
573
595
|
script: appScriptName(input.app.key, input.app.copy),
|
|
574
596
|
shape: workerAppShape(app),
|
|
575
597
|
vars: Object.entries(vars).sort(([a], [b]) => a.localeCompare(b)),
|
|
576
|
-
|
|
598
|
+
// An unset held secret (no value, or an empty one) is never set as an empty secret.
|
|
599
|
+
secrets: Object.fromEntries(Object.entries(input.secrets).filter(([name]) => !unset.includes(name))),
|
|
600
|
+
unset: unset.sort(),
|
|
601
|
+
crons: input.app.copy === 'live' ? [...(app.triggers?.crons ?? [])].sort() : [],
|
|
577
602
|
database,
|
|
578
603
|
migrationsNote: Boolean(app.database?.migrationsSha256),
|
|
579
604
|
};
|
|
@@ -649,6 +674,9 @@ export async function deployWorkerApp(input, deps) {
|
|
|
649
674
|
const parsed = prepared.database;
|
|
650
675
|
hidden.push(parsed.password);
|
|
651
676
|
}
|
|
677
|
+
if (prepared.crons.length > 0 && !c.cf.setWorkerSchedules) {
|
|
678
|
+
throw new Stop('check', 'This app runs scheduled jobs, and the connection to your Cloudflare account cannot set them yet. Update Boule, then try again.');
|
|
679
|
+
}
|
|
652
680
|
const address = await findAddress(c, input.app, script);
|
|
653
681
|
await checkAddressFree(c, address, script);
|
|
654
682
|
const module = await fetchBuild(c, prepared, input.github ?? null);
|
|
@@ -691,6 +719,10 @@ export async function deployWorkerApp(input, deps) {
|
|
|
691
719
|
if (!existing)
|
|
692
720
|
await putSecrets(c, script, prepared.secrets);
|
|
693
721
|
await pruneSecrets(c, script, prepared.secrets);
|
|
722
|
+
if (prepared.unset.length > 0) {
|
|
723
|
+
c.steps.push(`No value was given for ${prepared.unset.join(', ')}, so the app runs without ${prepared.unset.length === 1 ? 'it' : 'them'}. Add ${prepared.unset.length === 1 ? 'it' : 'them'} under the app’s settings in Boule and deploy again.`);
|
|
724
|
+
}
|
|
725
|
+
await syncSchedules(c, script, prepared.crons, input.app.copy);
|
|
694
726
|
if (!prepared.database)
|
|
695
727
|
await deleteHyperdrive(c, 'database', script);
|
|
696
728
|
await syncContainers(c, script, shape.containers);
|
|
@@ -36,6 +36,8 @@ export interface ApplyWorkerAppMigrationsInput {
|
|
|
36
36
|
migrationsSha256?: string | undefined;
|
|
37
37
|
/** build.json's `database.appendOnly`. */
|
|
38
38
|
appendOnly?: string[] | undefined;
|
|
39
|
+
/** The entry's `database.noDelete`: tables the app's user may not delete from. */
|
|
40
|
+
noDelete?: string[] | undefined;
|
|
39
41
|
/** build.json's `database.grants`. */
|
|
40
42
|
grants?: string[] | undefined;
|
|
41
43
|
}
|
|
@@ -70,6 +72,7 @@ export interface WorkerAppDatabaseRunner extends WorkerAppMigrationRunner<Databa
|
|
|
70
72
|
runtimeRolePrivileges(owner: DatabaseOwnerAddress, input: {
|
|
71
73
|
user: string;
|
|
72
74
|
appendOnly: string[];
|
|
75
|
+
noDelete: string[];
|
|
73
76
|
grants: string[];
|
|
74
77
|
}): Promise<RuntimeRolePrivileges>;
|
|
75
78
|
/** Closes every connection the runner has open now, without waiting for the work on it. */
|
package/dist/apps/migrations.js
CHANGED
|
@@ -84,11 +84,19 @@ function check(input) {
|
|
|
84
84
|
if (!Array.isArray(appendOnly) || appendOnly.length > LIST_MAX) {
|
|
85
85
|
throw new Stop('check', `At most ${LIST_MAX} append-only tables.`);
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
const noDelete = input.noDelete ?? [];
|
|
88
|
+
if (!Array.isArray(noDelete) || noDelete.length > LIST_MAX) {
|
|
89
|
+
throw new Stop('check', `At most ${LIST_MAX} tables the app may not delete from.`);
|
|
90
|
+
}
|
|
91
|
+
for (const table of [...appendOnly, ...noDelete]) {
|
|
88
92
|
if (typeof table !== 'string' || !WORKER_APP_TABLE_NAME.test(table)) {
|
|
89
93
|
throw new Stop('check', `${String(table).slice(0, 80)} is not a table name (letters, digits and _, optionally schema.table).`);
|
|
90
94
|
}
|
|
91
95
|
}
|
|
96
|
+
const both = noDelete.find((table) => appendOnly.includes(table));
|
|
97
|
+
if (both) {
|
|
98
|
+
throw new Stop('check', `${both} is listed as append-only and as no-delete. Append-only already stops deleting; list it once.`);
|
|
99
|
+
}
|
|
92
100
|
if (!Array.isArray(grants) || grants.length > LIST_MAX) {
|
|
93
101
|
throw new Stop('check', `At most ${LIST_MAX} granted functions.`);
|
|
94
102
|
}
|
|
@@ -155,6 +163,7 @@ export async function applyWorkerAppMigrations(input, deps = {}) {
|
|
|
155
163
|
const ms = deps.timeoutMs ?? CALL_TIMEOUT_MS;
|
|
156
164
|
const ownerAddress = { url: input.ownerUrl };
|
|
157
165
|
const appendOnly = input.appendOnly ?? [];
|
|
166
|
+
const noDelete = input.noDelete ?? [];
|
|
158
167
|
const grants = input.grants ?? [];
|
|
159
168
|
const call = async (label, task, over) => {
|
|
160
169
|
const step = label.startsWith('apply migration')
|
|
@@ -207,6 +216,7 @@ export async function applyWorkerAppMigrations(input, deps = {}) {
|
|
|
207
216
|
}
|
|
208
217
|
const result = await runWorkerAppMigrations(sql, ownerAddress, input.migrationsJson, {
|
|
209
218
|
appendOnly,
|
|
219
|
+
noDelete,
|
|
210
220
|
grants,
|
|
211
221
|
...(input.migrationsSha256 ? { migrationsSha256: input.migrationsSha256 } : {}),
|
|
212
222
|
}, { user: runtime.user, password: runtime.password }, {
|
|
@@ -226,8 +236,13 @@ export async function applyWorkerAppMigrations(input, deps = {}) {
|
|
|
226
236
|
// The only check left after `check`: an applied migration moved or left the file.
|
|
227
237
|
throw new Stop('migrations', 'A database change that already ran is no longer in the same place in migrations.json. Changes are only ever added at the end; restore the file’s earlier order in a new release.');
|
|
228
238
|
});
|
|
229
|
-
const held = await call('ensure: check the app’s database user', () => sql.runtimeRolePrivileges(ownerAddress, {
|
|
230
|
-
|
|
239
|
+
const held = await call('ensure: check the app’s database user', () => sql.runtimeRolePrivileges(ownerAddress, {
|
|
240
|
+
user: runtime.user,
|
|
241
|
+
appendOnly,
|
|
242
|
+
noDelete,
|
|
243
|
+
grants,
|
|
244
|
+
}));
|
|
245
|
+
proveRole(held, appendOnly, noDelete, grants);
|
|
231
246
|
if (result.ran.length === 0)
|
|
232
247
|
steps.push('The database already had every change of this build');
|
|
233
248
|
steps.push(`Set up the app’s database user ${runtime.user}`);
|
|
@@ -254,7 +269,7 @@ export async function applyWorkerAppMigrations(input, deps = {}) {
|
|
|
254
269
|
const named = (id) => (id && MIGRATION_ID.test(id) ? ` ${id}` : '');
|
|
255
270
|
const editedMessage = (id) => `The database change${named(id)} was edited after it ran. A change that ran is never edited: add a new one in a new release.`;
|
|
256
271
|
/** The role as read back must be what build.json asks for; else the `role` step fails, naming what. */
|
|
257
|
-
function proveRole(held, appendOnly, grants) {
|
|
272
|
+
function proveRole(held, appendOnly, noDelete, grants) {
|
|
258
273
|
for (const name of appendOnly) {
|
|
259
274
|
const table = held.tables.find((t) => t.name === name);
|
|
260
275
|
if (!table?.exists) {
|
|
@@ -264,6 +279,15 @@ function proveRole(held, appendOnly, grants) {
|
|
|
264
279
|
throw new Stop('role', `The app’s database user can still change or delete rows of ${name}, which build.json says only grows. Write the name in build.json as the table is named, and remove any grant on it to PUBLIC or to a role that user belongs to; then try again.`);
|
|
265
280
|
}
|
|
266
281
|
}
|
|
282
|
+
for (const name of noDelete) {
|
|
283
|
+
const table = held.tables.find((t) => t.name === name);
|
|
284
|
+
if (!table?.exists) {
|
|
285
|
+
throw new Stop('role', `The table ${name} that may not be deleted from is not in the database after its changes ran. Write it in the app's database settings as the table is named, then try again.`);
|
|
286
|
+
}
|
|
287
|
+
if (table.delete || table.truncate) {
|
|
288
|
+
throw new Stop('role', `The app’s database user can still delete rows of ${name}, which is set to keep its rows. Write the name as the table is named, and remove any grant on it to PUBLIC or to a role that user belongs to; then try again.`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
267
291
|
for (const signature of grants) {
|
|
268
292
|
const fn = held.functions.find((f) => f.signature === signature);
|
|
269
293
|
if (!fn?.exists) {
|
|
@@ -375,13 +399,14 @@ export function postgresMigrationRunner() {
|
|
|
375
399
|
return row.comment === runtimeRoleMarker(database) ? 'ours' : 'other';
|
|
376
400
|
});
|
|
377
401
|
},
|
|
378
|
-
async ensureRuntimeRole({ owner, runtime, appendOnly, grants }) {
|
|
402
|
+
async ensureRuntimeRole({ owner, runtime, appendOnly, noDelete, grants }) {
|
|
379
403
|
const { ensureRuntimeRole } = await import('@wtfalch/db/runtime-role');
|
|
380
404
|
// Not abortable: `@wtfalch/db` opens its own connection. Every statement of it is idempotent.
|
|
381
405
|
await ensureRuntimeRole({
|
|
382
406
|
ownerUrl: owner.url,
|
|
383
407
|
runtimeUrl: runtimeUrlFor(owner.url, runtime.user, runtime.password),
|
|
384
408
|
appendOnly,
|
|
409
|
+
noDelete,
|
|
385
410
|
grants,
|
|
386
411
|
log: () => { },
|
|
387
412
|
});
|
|
@@ -393,7 +418,7 @@ export function postgresMigrationRunner() {
|
|
|
393
418
|
await db.database.query(`comment on role ${quoted.role} is ${quoted.note}`);
|
|
394
419
|
});
|
|
395
420
|
},
|
|
396
|
-
async runtimeRolePrivileges({ url }, { user, appendOnly, grants }) {
|
|
421
|
+
async runtimeRolePrivileges({ url }, { user, appendOnly, noDelete, grants }) {
|
|
397
422
|
return withOwner(url, async (owner) => {
|
|
398
423
|
// A right the role holds itself, through PUBLIC, or through a role it belongs to (a
|
|
399
424
|
// NOINHERIT member can still SET ROLE to it).
|
|
@@ -403,7 +428,7 @@ export function postgresMigrationRunner() {
|
|
|
403
428
|
where r.rolname <> $1::name and pg_has_role($1::name, r.oid, 'MEMBER')
|
|
404
429
|
and has_table_privilege(r.oid, c.oid, '${privilege}')))`;
|
|
405
430
|
const tables = [];
|
|
406
|
-
for (const name of appendOnly) {
|
|
431
|
+
for (const name of [...appendOnly, ...noDelete]) {
|
|
407
432
|
const [row] = await owner.database.query(`select c.oid is not null as exists,
|
|
408
433
|
coalesce(${holds('UPDATE')}, false) as update,
|
|
409
434
|
coalesce(${holds('DELETE')}, false) as delete,
|
|
@@ -77,6 +77,11 @@ const INTEGRATION_COPY = {
|
|
|
77
77
|
meters: { tokens: 'AI use' },
|
|
78
78
|
outputs: { address: 'The address the app calls for AI tasks.' },
|
|
79
79
|
},
|
|
80
|
+
analytics: {
|
|
81
|
+
name: 'Count visitors',
|
|
82
|
+
summary: 'We count how many people visit the app and which pages they open, for each release.',
|
|
83
|
+
ours: 'Run by us',
|
|
84
|
+
},
|
|
80
85
|
organisations: {
|
|
81
86
|
name: 'Sign-in for organisations',
|
|
82
87
|
summary: 'Companies sign their own people in to the app.',
|
|
@@ -76,6 +76,11 @@ const EXTRA = {
|
|
|
76
76
|
price: { class: 'usage', meters: [meter('audit', 'entries', 'entries')] },
|
|
77
77
|
advanced: [],
|
|
78
78
|
},
|
|
79
|
+
analytics: {
|
|
80
|
+
providers: [ours('analytics')],
|
|
81
|
+
price: { class: 'free', meters: [] },
|
|
82
|
+
advanced: [],
|
|
83
|
+
},
|
|
79
84
|
organisations: {
|
|
80
85
|
providers: [ours('organisations')],
|
|
81
86
|
price: { class: 'free', meters: [] },
|
package/dist/entry-rules.d.ts
CHANGED
|
@@ -48,6 +48,17 @@ export declare const WORKER_APP_ENV_NAME: RegExp;
|
|
|
48
48
|
/** The most tables `database.appendOnly` may name, and the shape of one name (`table` or `schema.table`). */
|
|
49
49
|
export declare const WORKER_APP_APPEND_ONLY_MAX = 100;
|
|
50
50
|
export declare const WORKER_APP_TABLE_NAME: RegExp;
|
|
51
|
+
/** The most tables `database.noDelete` may name; the names follow {@link WORKER_APP_TABLE_NAME}. */
|
|
52
|
+
export declare const WORKER_APP_NO_DELETE_MAX = 100;
|
|
51
53
|
/** The most functions `database.grants` may name, and the shape of one signature. It is spliced into SQL, so nothing else passes. */
|
|
52
54
|
export declare const WORKER_APP_GRANTS_MAX = 100;
|
|
53
55
|
export declare const WORKER_APP_FUNCTION_SIGNATURE: RegExp;
|
|
56
|
+
/** Cloudflare allows at most three cron triggers on one Worker. */
|
|
57
|
+
export declare const WORKER_APP_CRONS_MAX = 3;
|
|
58
|
+
/**
|
|
59
|
+
* `null` when `expression` is a standard five-field cron (minute hour day-of-month month
|
|
60
|
+
* day-of-week) built from `*`, numbers, `a-b` ranges, `/step` and comma lists, with month and
|
|
61
|
+
* weekday names allowed; else a plain sentence that names the field and repeats no more than the
|
|
62
|
+
* first 40 characters of the input. Cloudflare's extensions (`L`, `W`, `#`) are refused.
|
|
63
|
+
*/
|
|
64
|
+
export declare function workerAppCronProblem(expression: unknown): string | null;
|
package/dist/entry-rules.js
CHANGED
|
@@ -66,6 +66,76 @@ export const WORKER_APP_ENV_NAME = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
|
66
66
|
/** The most tables `database.appendOnly` may name, and the shape of one name (`table` or `schema.table`). */
|
|
67
67
|
export const WORKER_APP_APPEND_ONLY_MAX = 100;
|
|
68
68
|
export const WORKER_APP_TABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,62}(\.[A-Za-z_][A-Za-z0-9_]{0,62})?$/;
|
|
69
|
+
/** The most tables `database.noDelete` may name; the names follow {@link WORKER_APP_TABLE_NAME}. */
|
|
70
|
+
export const WORKER_APP_NO_DELETE_MAX = 100;
|
|
69
71
|
/** The most functions `database.grants` may name, and the shape of one signature. It is spliced into SQL, so nothing else passes. */
|
|
70
72
|
export const WORKER_APP_GRANTS_MAX = 100;
|
|
71
73
|
export const WORKER_APP_FUNCTION_SIGNATURE = /^[A-Za-z_][A-Za-z0-9_]{0,62}(\.[A-Za-z_][A-Za-z0-9_]{0,62})?\(([a-z][a-z0-9_]*( [a-z][a-z0-9_]*)?(, [a-z][a-z0-9_]*( [a-z][a-z0-9_]*)?)*)?\)$/;
|
|
74
|
+
/** Cloudflare allows at most three cron triggers on one Worker. */
|
|
75
|
+
export const WORKER_APP_CRONS_MAX = 3;
|
|
76
|
+
const MONTH_NAMES = [
|
|
77
|
+
'JAN',
|
|
78
|
+
'FEB',
|
|
79
|
+
'MAR',
|
|
80
|
+
'APR',
|
|
81
|
+
'MAY',
|
|
82
|
+
'JUN',
|
|
83
|
+
'JUL',
|
|
84
|
+
'AUG',
|
|
85
|
+
'SEP',
|
|
86
|
+
'OCT',
|
|
87
|
+
'NOV',
|
|
88
|
+
'DEC',
|
|
89
|
+
];
|
|
90
|
+
const WEEKDAY_NAMES = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'];
|
|
91
|
+
const CRON_FIELDS = [
|
|
92
|
+
{ name: 'minute', min: 0, max: 59 },
|
|
93
|
+
{ name: 'hour', min: 0, max: 23 },
|
|
94
|
+
{ name: 'day of the month', min: 1, max: 31 },
|
|
95
|
+
{ name: 'month', min: 1, max: 12, names: MONTH_NAMES, first: 1 },
|
|
96
|
+
{ name: 'day of the week', min: 0, max: 7, names: WEEKDAY_NAMES, first: 0 },
|
|
97
|
+
];
|
|
98
|
+
/**
|
|
99
|
+
* `null` when `expression` is a standard five-field cron (minute hour day-of-month month
|
|
100
|
+
* day-of-week) built from `*`, numbers, `a-b` ranges, `/step` and comma lists, with month and
|
|
101
|
+
* weekday names allowed; else a plain sentence that names the field and repeats no more than the
|
|
102
|
+
* first 40 characters of the input. Cloudflare's extensions (`L`, `W`, `#`) are refused.
|
|
103
|
+
*/
|
|
104
|
+
export function workerAppCronProblem(expression) {
|
|
105
|
+
if (typeof expression !== 'string')
|
|
106
|
+
return 'it is not text';
|
|
107
|
+
const shown = JSON.stringify(expression.slice(0, 40));
|
|
108
|
+
const fields = expression.split(' ');
|
|
109
|
+
if (fields.length !== 5 || fields.some((field) => field === '')) {
|
|
110
|
+
return `${shown} must be five fields separated by single spaces: minute hour day-of-month month day-of-week`;
|
|
111
|
+
}
|
|
112
|
+
for (const [index, field] of fields.entries()) {
|
|
113
|
+
const spec = CRON_FIELDS[index];
|
|
114
|
+
const value = (token) => {
|
|
115
|
+
if (/^[0-9]{1,2}$/.test(token)) {
|
|
116
|
+
const n = Number(token);
|
|
117
|
+
return n >= spec.min && n <= spec.max ? n : null;
|
|
118
|
+
}
|
|
119
|
+
const at = spec.names?.indexOf(token.toUpperCase()) ?? -1;
|
|
120
|
+
return at >= 0 ? at + (spec.first ?? 0) : null;
|
|
121
|
+
};
|
|
122
|
+
for (const item of field.split(',')) {
|
|
123
|
+
const [base = '', step, extra] = item.split('/');
|
|
124
|
+
if (extra !== undefined || (step !== undefined && !/^[0-9]{1,2}$/.test(step))) {
|
|
125
|
+
return `${shown} has a step in the ${spec.name} field that is not a whole number`;
|
|
126
|
+
}
|
|
127
|
+
if (step !== undefined && Number(step) < 1) {
|
|
128
|
+
return `${shown} has a step of 0 in the ${spec.name} field`;
|
|
129
|
+
}
|
|
130
|
+
if (base === '*')
|
|
131
|
+
continue;
|
|
132
|
+
const [from, to, more] = base.split('-');
|
|
133
|
+
const a = from === undefined ? null : value(from);
|
|
134
|
+
const b = to === undefined ? a : value(to);
|
|
135
|
+
if (more !== undefined || a === null || b === null || a > b) {
|
|
136
|
+
return `${shown} has a ${spec.name} that is not ${spec.min} to ${spec.max}${spec.names ? ' or a name' : ''}`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,14 +22,18 @@ export { StaleLeaseError } from './store.js';
|
|
|
22
22
|
export type { PlanApply, DestroyProgress } from './apply.js';
|
|
23
23
|
export { createPostgresAppliedVersionsStore, createPostgresDeclaredOrgStore, createPostgresPlatformStore, createPostgresRequestedChangeStore, DeclaredOrgError, PlatformError, } from './store/postgres.js';
|
|
24
24
|
export { createPostgresAuditLog } from './store/audit.js';
|
|
25
|
-
export type { Color, ReleaseAction, ReleaseSignal, ReleaseState, WindowState, } from './release/types.js';
|
|
25
|
+
export type { Color, ReleaseAction, ReleaseEvent, ReleaseEventKind, ReleaseSeries, ReleaseSeriesColor, ReleaseSignal, ReleaseState, WindowState, } from './release/types.js';
|
|
26
26
|
export type { ReleaseStore } from './release/state.js';
|
|
27
|
-
export { readReleaseState, requestReleaseAction } from './release/state.js';
|
|
27
|
+
export { readReleaseHistory, readReleaseState, requestReleaseAction } from './release/state.js';
|
|
28
|
+
export type { ReleaseSeriesHost, ReleaseSeriesQuery } from './release/series.js';
|
|
29
|
+
export { readReleaseSeries } from './release/series.js';
|
|
30
|
+
export type { EntrySeriesDeps } from './plan/release-series-reader.js';
|
|
31
|
+
export { readEntryReleaseSeries } from './plan/release-series-reader.js';
|
|
28
32
|
export type { DeclaredClassRemoval, MigrationCompatibilityInput, MigrationCompatibilityProblem, MigrationCompatibilityResult, } from './release/migration-compatibility.js';
|
|
29
33
|
export { checkMigrationCompatibility } from './release/migration-compatibility.js';
|
|
30
34
|
export { createPostgresReleaseStore } from './store/postgres.js';
|
|
31
35
|
export type { FoundryStoreDb } from './store/postgres.js';
|
|
32
|
-
export { declaredOrgs, foundryAppliedState, foundryAppliedVersions, foundryAuditEvents, foundryDeclaredPlatform, foundryReleaseRequests, foundryRequestedChanges, type AuditEventRow, type DeclaredOrgRow, type NewDeclaredOrgRow, type AppliedStateRow, type NewAppliedStateRow, type AppliedVersionRow, type NewAppliedVersionRow, type DeclaredPlatformRow, type NewDeclaredPlatformRow, type RequestedChangeRow, type NewRequestedChangeRow, } from './store/schema.js';
|
|
36
|
+
export { declaredOrgs, foundryAppliedState, foundryAppliedVersions, foundryAuditEvents, foundryDeclaredPlatform, foundryReleaseEvents, foundryReleaseRequests, foundryRequestedChanges, type AuditEventRow, type DeclaredOrgRow, type NewDeclaredOrgRow, type AppliedStateRow, type NewAppliedStateRow, type AppliedVersionRow, type NewAppliedVersionRow, type DeclaredPlatformRow, type NewDeclaredPlatformRow, type RequestedChangeRow, type NewRequestedChangeRow, } from './store/schema.js';
|
|
33
37
|
export type { LiveResource, ZitadelReader, CoolifyReader, CloudflareReader, FilesReader, AiReader, CmsReader, LiveReaders, PlatformCloudflareReader, PlatformCoolifyReader, PlatformLiveReaders, StepAction, PlannedStep, StepSystem, PlanDetail, OrgPostgresPlan, PlatformPlanDetail, MailRoute, AdoptCandidate, AdoptInput, AdoptPlan, AdoptSnapshot, AuditActor, AuditLog, AuthzGate, StepExecutor, StepDestroyer, DestroyTarget, StepExecutionContext, PlatformStepExecutor, PlatformStepExecutionContext, FoundryPlanApplyOptions, FoundryApplyOptions, UnconfirmedUpgrade, } from './plan/index.js';
|
|
34
38
|
export { COMPANY_DESTINATION_NAME_PREFIX, COMPANY_NETWORKS, DEFAULT_MAX_DESTINATIONS, DestinationCapacityError, PLATFORM_NETWORK, companyNetworkName, ensureDestination, findDestination, placed, placedOnPlatform, validateNetworkName, } from './plan/index.js';
|
|
35
39
|
export { AdoptRefusedError } from './plan/index.js';
|
|
@@ -59,7 +63,7 @@ export type { FilesHostInput, JobsHostDeps, JobsHostInput, OwnAuthInstance, Plan
|
|
|
59
63
|
export { OWN_AUTH_APPLIED_GIT_REF_SCOPE_KEY, OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY, OWN_AUTH_BLOCKED_KEY, OWN_FILES_BLOCKED_KEY, OWN_FILES_R2_BLOCKED_KEY, OWN_JOBS_BLOCKED_KEY, OWN_MAIL_BLOCKED_KEY, planOwnAuth, planOwnFiles, planOwnFilesR2, planOwnJobs, planOwnMail, provisionFilesHost, provisionJobsHost, provisionMailServer, } from './plan/index.js';
|
|
60
64
|
export type { PlanOwnSfuOptions, SfuAppDeps, SfuAppInput } from './plan/index.js';
|
|
61
65
|
export { OWN_SFU_BLOCKED_KEY, OWN_SFU_FIREWALL_KEY, planOwnSfu, provisionSfuApp, sfuConfig, sfuCompose, } from './plan/index.js';
|
|
62
|
-
export type { CoolifyPort, CloudflarePort, ZitadelPort, CallsSfuPort, JobsSchedulerPort, FilesOrganisationPort, AiOrganisationPort, FoundryStepExecutorPorts, KeysRegistrarPort, PlatformOrgPortSource, AgoraHostPort, AgoraHostValues, LoginHostBinding, LoginHostBindingsPort, LoginHostDiscoveryPort, } from './plan/index.js';
|
|
66
|
+
export type { CoolifyPort, CloudflarePort, ZitadelPort, CallsSfuPort, JobsSchedulerPort, FilesOrganisationPort, AiOrganisationPort, FoundryStepExecutorPorts, KeysRegistrarPort, AnalyticsPort, PlatformOrgPortSource, AgoraHostPort, AgoraHostValues, LoginHostBinding, LoginHostBindingsPort, LoginHostDiscoveryPort, } from './plan/index.js';
|
|
63
67
|
export { FoundryStepExecutor, NotBuiltError, healthUrls, loginHostnameFor } from './plan/index.js';
|
|
64
68
|
export type { StageProbePort, ReleasePreviewMembers } from './plan/stage-guards.js';
|
|
65
69
|
export type { StageCopyHostConfig } from './plan/stage-copy-host.js';
|
|
@@ -71,6 +75,7 @@ export type { OwnHostnames } from './plan/index.js';
|
|
|
71
75
|
export type { EnsureSignInOrgOptions } from './plan/index.js';
|
|
72
76
|
export { ZITADEL_ORG_DOMAIN_SCOPE_KEY, ensureSignInOrg, registerSignInDomain, signInOrgDomain, } from './plan/index.js';
|
|
73
77
|
export { HttpJobsSchedulerClient } from './plan/index.js';
|
|
78
|
+
export { httpAnalytics } from './plan/index.js';
|
|
74
79
|
export { httpKeysRegistrar, keysRegistrarFromConfig, KEYS_PLATFORM_TOKEN_ENV, KEYS_SERVICE_URL_ENV, } from './plan/index.js';
|
|
75
80
|
export type { HealthTarget, HealthTargetDepth, HealthTargetExpect } from './plan/index.js';
|
|
76
81
|
export { healthTargets, platformHealthTargets } from './plan/index.js';
|
package/dist/index.js
CHANGED
|
@@ -14,10 +14,12 @@ export { NATIVE_BUILD_REPO, NATIVE_BUILD_WORKFLOW_FILE, NATIVE_BUILD_WORKFLOW_RE
|
|
|
14
14
|
export { StaleLeaseError } from './store.js';
|
|
15
15
|
export { createPostgresAppliedVersionsStore, createPostgresDeclaredOrgStore, createPostgresPlatformStore, createPostgresRequestedChangeStore, DeclaredOrgError, PlatformError, } from './store/postgres.js';
|
|
16
16
|
export { createPostgresAuditLog } from './store/audit.js';
|
|
17
|
-
export { readReleaseState, requestReleaseAction } from './release/state.js';
|
|
17
|
+
export { readReleaseHistory, readReleaseState, requestReleaseAction } from './release/state.js';
|
|
18
|
+
export { readReleaseSeries } from './release/series.js';
|
|
19
|
+
export { readEntryReleaseSeries } from './plan/release-series-reader.js';
|
|
18
20
|
export { checkMigrationCompatibility } from './release/migration-compatibility.js';
|
|
19
21
|
export { createPostgresReleaseStore } from './store/postgres.js';
|
|
20
|
-
export { declaredOrgs, foundryAppliedState, foundryAppliedVersions, foundryAuditEvents, foundryDeclaredPlatform, foundryReleaseRequests, foundryRequestedChanges, } from './store/schema.js';
|
|
22
|
+
export { declaredOrgs, foundryAppliedState, foundryAppliedVersions, foundryAuditEvents, foundryDeclaredPlatform, foundryReleaseEvents, foundryReleaseRequests, foundryRequestedChanges, } from './store/schema.js';
|
|
21
23
|
export { COMPANY_DESTINATION_NAME_PREFIX, COMPANY_NETWORKS, DEFAULT_MAX_DESTINATIONS, DestinationCapacityError, PLATFORM_NETWORK, companyNetworkName, ensureDestination, findDestination, placed, placedOnPlatform, validateNetworkName, } from './plan/index.js';
|
|
22
24
|
export { AdoptRefusedError } from './plan/index.js';
|
|
23
25
|
export { DumpError, dumpNow } from './plan/index.js';
|
|
@@ -39,6 +41,7 @@ export { NO_DOMAIN_ZONE, entryMachinePaths, ownHostnames, privatePageGateKey, wo
|
|
|
39
41
|
export { hostedLabelRefusal } from './hosted-label.js';
|
|
40
42
|
export { ZITADEL_ORG_DOMAIN_SCOPE_KEY, ensureSignInOrg, registerSignInDomain, signInOrgDomain, } from './plan/index.js';
|
|
41
43
|
export { HttpJobsSchedulerClient } from './plan/index.js';
|
|
44
|
+
export { httpAnalytics } from './plan/index.js';
|
|
42
45
|
export { httpKeysRegistrar, keysRegistrarFromConfig, KEYS_PLATFORM_TOKEN_ENV, KEYS_SERVICE_URL_ENV, } from './plan/index.js';
|
|
43
46
|
export { healthTargets, platformHealthTargets } from './plan/index.js';
|
|
44
47
|
export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeLoginHostResolver, storeScopeResolver, storeRecordedIds, } from './plan/index.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- @wtfalch/foundry: a release's history, one line per step, check, action and outcome, written by the
|
|
2
|
+
-- release run. Append-only: nothing in the package updates or deletes a row. Additive: a new table, no
|
|
3
|
+
-- existing row touched. Copies 0055_release_requests.sql (no foreign key, because `org_id` may be
|
|
4
|
+
-- `platform`). Mirrors src/store/schema.ts's `foundryReleaseEvents`.
|
|
5
|
+
|
|
6
|
+
create table if not exists foundry_release_events (
|
|
7
|
+
id bigserial primary key,
|
|
8
|
+
org_id text not null,
|
|
9
|
+
entry_key text not null,
|
|
10
|
+
at timestamptz not null,
|
|
11
|
+
kind text not null,
|
|
12
|
+
payload jsonb not null default '{}'::jsonb
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
create index if not exists foundry_release_events_entry_idx
|
|
16
|
+
on foundry_release_events (org_id, entry_key, at desc, id desc);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AppModule } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* `uses.analytics` on a Worker app: the app counts its visitors and page views. Boule installs a site key of the
|
|
4
|
+
* company's own analytics backend on each copy of the app (`ANALYTICS_URL`, `ANALYTICS_SITE_KEY`, and the
|
|
5
|
+
* `CF_VERSION_METADATA` binding the app reads its release from); foundry only sets up the backend. One on/off
|
|
6
|
+
* module, no fields. A server app declares `uses.analytics` itself and gets its key from foundry.
|
|
7
|
+
*/
|
|
8
|
+
export declare const analytics: AppModule;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { setUse } from './entry-parts.js';
|
|
2
|
+
/**
|
|
3
|
+
* `uses.analytics` on a Worker app: the app counts its visitors and page views. Boule installs a site key of the
|
|
4
|
+
* company's own analytics backend on each copy of the app (`ANALYTICS_URL`, `ANALYTICS_SITE_KEY`, and the
|
|
5
|
+
* `CF_VERSION_METADATA` binding the app reads its release from); foundry only sets up the backend. One on/off
|
|
6
|
+
* module, no fields. A server app declares `uses.analytics` itself and gets its key from foundry.
|
|
7
|
+
*/
|
|
8
|
+
export const analytics = {
|
|
9
|
+
view: {
|
|
10
|
+
id: 'analytics',
|
|
11
|
+
name: 'Count visitors',
|
|
12
|
+
line: 'We count how many people visit the app and which pages they open, for each release.',
|
|
13
|
+
category: 'Data',
|
|
14
|
+
icon: 'chart',
|
|
15
|
+
runtimes: ['worker'],
|
|
16
|
+
requires: [],
|
|
17
|
+
availability: 'ready',
|
|
18
|
+
fields: [],
|
|
19
|
+
adds: [
|
|
20
|
+
{
|
|
21
|
+
name: 'Visitor numbers',
|
|
22
|
+
hint: 'Visitors and page views for every release, on the release page. Kept in your own company only.',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
toEntry: (entry, values) => setUse(entry, 'analytics', values === null ? undefined : true),
|
|
27
|
+
fromEntry: (entry) => (entry.uses?.analytics === true ? {} : null),
|
|
28
|
+
};
|
package/dist/modules/database.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WORKER_APP_APPEND_ONLY_MAX, WORKER_APP_FUNCTION_SIGNATURE, WORKER_APP_GRANTS_MAX, WORKER_APP_TABLE_NAME, } from '../entry-rules.js';
|
|
1
|
+
import { WORKER_APP_APPEND_ONLY_MAX, WORKER_APP_FUNCTION_SIGNATURE, WORKER_APP_GRANTS_MAX, WORKER_APP_NO_DELETE_MAX, WORKER_APP_TABLE_NAME, } from '../entry-rules.js';
|
|
2
2
|
import { clone, list, omit, problem, withWorkerApp } from './entry-parts.js';
|
|
3
3
|
const ADDRESS_HELP = 'From your Postgres provider. The owner can change tables; the app address is a separate user with less access. Use a different database for the workspace than for live.';
|
|
4
4
|
const ADDRESS_MAX = 2000;
|
|
@@ -112,6 +112,15 @@ export const database = {
|
|
|
112
112
|
pattern: WORKER_APP_TABLE_NAME.source,
|
|
113
113
|
max: WORKER_APP_APPEND_ONLY_MAX,
|
|
114
114
|
},
|
|
115
|
+
{
|
|
116
|
+
key: 'noDelete',
|
|
117
|
+
label: 'Tables the app cannot delete from',
|
|
118
|
+
help: 'We stop the app from deleting rows in these tables. It can still add, read and change them, and it clears old rows through a database function you allow below. Only for apps that run as a Worker app.',
|
|
119
|
+
kind: 'list',
|
|
120
|
+
advanced: true,
|
|
121
|
+
pattern: WORKER_APP_TABLE_NAME.source,
|
|
122
|
+
max: WORKER_APP_NO_DELETE_MAX,
|
|
123
|
+
},
|
|
115
124
|
{
|
|
116
125
|
key: 'grants',
|
|
117
126
|
label: 'Database functions the app may run',
|
|
@@ -132,13 +141,18 @@ export const database = {
|
|
|
132
141
|
},
|
|
133
142
|
check(values, _modules, ctx) {
|
|
134
143
|
if (ctx.runtime === 'server') {
|
|
135
|
-
for (const key of ['appendOnly', 'grants']) {
|
|
144
|
+
for (const key of ['appendOnly', 'noDelete', 'grants']) {
|
|
136
145
|
if (list(values, key) !== undefined) {
|
|
137
|
-
return problem(key, '
|
|
146
|
+
return problem(key, 'Table protections and database functions can only be set for an app that runs as a Worker app.');
|
|
138
147
|
}
|
|
139
148
|
}
|
|
140
149
|
return null;
|
|
141
150
|
}
|
|
151
|
+
const kept = list(values, 'appendOnly') ?? [];
|
|
152
|
+
const both = (list(values, 'noDelete') ?? []).find((table) => kept.includes(table));
|
|
153
|
+
if (both) {
|
|
154
|
+
return problem('noDelete', `${both} is already in the tables that keep every row, which also stops deleting. Keep it in one list.`);
|
|
155
|
+
}
|
|
142
156
|
const workspace = readCopy(values, 'workspace');
|
|
143
157
|
if (workspace && 'message' in workspace)
|
|
144
158
|
return workspace;
|
|
@@ -161,9 +175,12 @@ export const database = {
|
|
|
161
175
|
return omit(app, 'database');
|
|
162
176
|
const next = {};
|
|
163
177
|
const appendOnly = list(values, 'appendOnly');
|
|
178
|
+
const noDelete = list(values, 'noDelete');
|
|
164
179
|
const grants = list(values, 'grants');
|
|
165
180
|
if (appendOnly)
|
|
166
181
|
next.appendOnly = appendOnly;
|
|
182
|
+
if (noDelete)
|
|
183
|
+
next.noDelete = noDelete;
|
|
167
184
|
if (grants)
|
|
168
185
|
next.grants = grants;
|
|
169
186
|
const pinned = app.database?.migrationsSha256;
|
|
@@ -180,6 +197,8 @@ export const database = {
|
|
|
180
197
|
const values = {};
|
|
181
198
|
if (db.appendOnly?.length)
|
|
182
199
|
values.appendOnly = [...db.appendOnly];
|
|
200
|
+
if (db.noDelete?.length)
|
|
201
|
+
values.noDelete = [...db.noDelete];
|
|
183
202
|
if (db.grants?.length)
|
|
184
203
|
values.grants = [...db.grants];
|
|
185
204
|
return values;
|
package/dist/modules/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SECRET_LIKE_NAME, WORKER_APP_ENV_NAME, WORKER_APP_RESERVED_NAMES, } from '../entry-rules.js';
|
|
2
2
|
import { ai } from './ai.js';
|
|
3
|
+
import { analytics } from './analytics.js';
|
|
3
4
|
import { assistants } from './assistants.js';
|
|
4
5
|
import { audit } from './audit.js';
|
|
5
6
|
import { customerGrants } from './customer-grants.js';
|
|
@@ -28,6 +29,7 @@ export const MODULES = [
|
|
|
28
29
|
files,
|
|
29
30
|
email,
|
|
30
31
|
jobs,
|
|
32
|
+
analytics,
|
|
31
33
|
ai,
|
|
32
34
|
organisations,
|
|
33
35
|
audit,
|
package/dist/native-build.d.ts
CHANGED
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
* builds and signs a company's native app shell (estate
|
|
4
4
|
* `.plans/2026-10-22-app-shells.md`). Boule now owns the `NativeAppConfig`,
|
|
5
5
|
* the build runs and the company's store keys (estate#262, foundry#951);
|
|
6
|
-
* foundry keeps only
|
|
7
|
-
* workflow, and the key Boule presents when it asks the host (Archon) to
|
|
8
|
-
* dispatch it.
|
|
6
|
+
* foundry keeps only the dispatch, in this file.
|
|
9
7
|
*
|
|
10
|
-
* `dispatchNativeBuild` runs inside Archon's own `/api/native/builds`
|
|
11
|
-
* route, not foundry's apply loop.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
8
|
+
* `dispatchNativeBuild` runs inside Archon's own `/api/orgs/{orgId}/native/builds`
|
|
9
|
+
* route, not foundry's apply loop.
|
|
10
|
+
*
|
|
11
|
+
* `NATIVE_BUILD_KEY_ENV` and `NATIVE_BUILD_KEY_BYTES` are deprecated
|
|
12
|
+
* (foundry#987): Archon's route now checks the company's own org credential
|
|
13
|
+
* instead (archon#706, boule#432), so nothing mints, delivers or reads
|
|
14
|
+
* `NATIVE_BUILD_KEY` any more -- `plan/executor.ts`'s `coolifyBoule` takes
|
|
15
|
+
* the env row away from any Boule that still has one from #970/#971. Both
|
|
16
|
+
* constants are kept one release for the two-release removal rule and go in
|
|
17
|
+
* the next.
|
|
18
18
|
*/
|
|
19
19
|
export declare const NATIVE_BUILD_REPO = "wtfalch/shell";
|
|
20
20
|
export declare const NATIVE_BUILD_WORKFLOW_FILE = "build-app.yml";
|
|
@@ -45,6 +45,10 @@ export declare function dispatchNativeBuild(deps: NativeBuildDispatchDeps, run:
|
|
|
45
45
|
runId: string;
|
|
46
46
|
origin: string;
|
|
47
47
|
}): Promise<void>;
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* @deprecated foundry#987: Archon's native-build route checks the company's own org credential
|
|
50
|
+
* now, not this key. Nothing mints, delivers or reads it any more. Removed in the next release.
|
|
51
|
+
*/
|
|
49
52
|
export declare const NATIVE_BUILD_KEY_ENV = "NATIVE_BUILD_KEY";
|
|
53
|
+
/** @deprecated foundry#987: goes with {@link NATIVE_BUILD_KEY_ENV}. */
|
|
50
54
|
export declare const NATIVE_BUILD_KEY_BYTES = 32;
|
package/dist/native-build.js
CHANGED
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
* builds and signs a company's native app shell (estate
|
|
4
4
|
* `.plans/2026-10-22-app-shells.md`). Boule now owns the `NativeAppConfig`,
|
|
5
5
|
* the build runs and the company's store keys (estate#262, foundry#951);
|
|
6
|
-
* foundry keeps only
|
|
7
|
-
* workflow, and the key Boule presents when it asks the host (Archon) to
|
|
8
|
-
* dispatch it.
|
|
6
|
+
* foundry keeps only the dispatch, in this file.
|
|
9
7
|
*
|
|
10
|
-
* `dispatchNativeBuild` runs inside Archon's own `/api/native/builds`
|
|
11
|
-
* route, not foundry's apply loop.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
8
|
+
* `dispatchNativeBuild` runs inside Archon's own `/api/orgs/{orgId}/native/builds`
|
|
9
|
+
* route, not foundry's apply loop.
|
|
10
|
+
*
|
|
11
|
+
* `NATIVE_BUILD_KEY_ENV` and `NATIVE_BUILD_KEY_BYTES` are deprecated
|
|
12
|
+
* (foundry#987): Archon's route now checks the company's own org credential
|
|
13
|
+
* instead (archon#706, boule#432), so nothing mints, delivers or reads
|
|
14
|
+
* `NATIVE_BUILD_KEY` any more -- `plan/executor.ts`'s `coolifyBoule` takes
|
|
15
|
+
* the env row away from any Boule that still has one from #970/#971. Both
|
|
16
|
+
* constants are kept one release for the two-release removal rule and go in
|
|
17
|
+
* the next.
|
|
18
18
|
*/
|
|
19
19
|
export const NATIVE_BUILD_REPO = 'wtfalch/shell';
|
|
20
20
|
export const NATIVE_BUILD_WORKFLOW_FILE = 'build-app.yml';
|
|
@@ -67,6 +67,10 @@ export async function dispatchNativeBuild(deps, run) {
|
|
|
67
67
|
if (!response.ok)
|
|
68
68
|
throw new NativeBuildDispatchError(response.status);
|
|
69
69
|
}
|
|
70
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* @deprecated foundry#987: Archon's native-build route checks the company's own org credential
|
|
72
|
+
* now, not this key. Nothing mints, delivers or reads it any more. Removed in the next release.
|
|
73
|
+
*/
|
|
71
74
|
export const NATIVE_BUILD_KEY_ENV = 'NATIVE_BUILD_KEY';
|
|
75
|
+
/** @deprecated foundry#987: goes with {@link NATIVE_BUILD_KEY_ENV}. */
|
|
72
76
|
export const NATIVE_BUILD_KEY_BYTES = 32;
|