@stacksjs/buddy 0.70.73 → 0.70.74
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/cli.d.ts +1 -1
- package/dist/commands/completion.js +1 -1
- package/dist/commands/deploy.d.ts +2 -2
- package/dist/commands/deploy.js +12 -9
- package/dist/commands/dev.js +8 -12
- package/dist/commands/doctor.js +6 -1
- package/dist/commands/email.js +3 -5
- package/dist/commands/features.d.ts +26 -18
- package/dist/commands/features.js +3 -3
- package/dist/commands/http.js +4 -1
- package/dist/commands/install.js +2 -3
- package/dist/commands/list.js +1 -1
- package/dist/commands/make.js +1 -1
- package/dist/commands/publish.js +4 -1
- package/dist/commands/serve.js +2 -1
- package/dist/commands/share.js +13 -8
- package/dist/config.js +1 -1
- package/dist/custom-cli.d.ts +1 -1
- package/dist/migrators/index.d.ts +1 -4
- package/dist/migrators/laravel/migrations.js +8 -7
- package/dist/migrators/laravel/models.js +8 -6
- package/package.json +8 -8
package/dist/cli.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
export {};
|
|
@@ -7,7 +7,7 @@ export function completion(buddy) {
|
|
|
7
7
|
};
|
|
8
8
|
buddy.command("completion [shell]", descriptions.completion).option("-s, --shell [shell]", descriptions.shell).example("buddy completion bash").example("buddy completion zsh").example("buddy completion fish").example("buddy completion bash > /usr/local/etc/bash_completion.d/buddy").action(async (shell, options) => {
|
|
9
9
|
log.debug("Running `buddy completion` ...", options);
|
|
10
|
-
const targetShell = shell || options.shell || "bash", commandNames = (buddy.commands || []).map((cmd) => cmd.name).filter(Boolean);
|
|
10
|
+
const targetShell = shell || options.shell || "bash", commandNames = (buddy.commands || []).map((cmd) => cmd.name).filter((name) => Boolean(name));
|
|
11
11
|
switch (targetShell) {
|
|
12
12
|
case "bash":
|
|
13
13
|
console.log(generateBashCompletion(commandNames));
|
|
@@ -85,7 +85,7 @@ export declare function deploy(buddy: CLI): void;
|
|
|
85
85
|
* success: (...args: any[]) => unknown,
|
|
86
86
|
* warn: (...args: any[]) => unknown,
|
|
87
87
|
* error: (...args: any[]) => unknown,
|
|
88
|
-
* debug: (...args: any[]) =>
|
|
88
|
+
* debug: (...args: any[]) => void
|
|
89
89
|
* }
|
|
90
90
|
* ```
|
|
91
91
|
*/
|
|
@@ -94,7 +94,7 @@ declare const log: {
|
|
|
94
94
|
success: (...args: any[]) => unknown;
|
|
95
95
|
warn: (...args: any[]) => unknown;
|
|
96
96
|
error: (...args: any[]) => unknown;
|
|
97
|
-
debug: (...args: any[]) =>
|
|
97
|
+
debug: (...args: any[]) => void
|
|
98
98
|
};
|
|
99
99
|
/** What a mail-tenant reconcile resolved + provisioned, for the DNS step. */
|
|
100
100
|
export declare interface MailTenantResult {
|
package/dist/commands/deploy.js
CHANGED
|
@@ -107,18 +107,20 @@ function loadAwsCredentialsFromFile() {
|
|
|
107
107
|
const profileCredentials = {};
|
|
108
108
|
for (const line of lines) {
|
|
109
109
|
const trimmed = line.trim(), profileMatch = trimmed.match(/^\[(.+)\]$/);
|
|
110
|
-
if (profileMatch) {
|
|
110
|
+
if (profileMatch?.[1]) {
|
|
111
111
|
currentProfile = profileMatch[1];
|
|
112
112
|
profileCredentials[currentProfile] = {};
|
|
113
113
|
continue;
|
|
114
114
|
}
|
|
115
115
|
const keyValue = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
|
|
116
116
|
if (keyValue && currentProfile) {
|
|
117
|
-
const [, key, value] = keyValue;
|
|
117
|
+
const [, key, value] = keyValue, target = profileCredentials[currentProfile];
|
|
118
|
+
if (!target || value === void 0)
|
|
119
|
+
continue;
|
|
118
120
|
if (key === "aws_access_key_id")
|
|
119
|
-
|
|
121
|
+
target.accessKeyId = value;
|
|
120
122
|
else if (key === "aws_secret_access_key")
|
|
121
|
-
|
|
123
|
+
target.secretAccessKey = value;
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
126
|
for (const profile of profiles)
|
|
@@ -138,7 +140,7 @@ function loadAwsCredentialsFromFile() {
|
|
|
138
140
|
let region;
|
|
139
141
|
if (existsSync(configPath)) {
|
|
140
142
|
const regionMatch = readFileSync(configPath, "utf-8").match(/region\s*=\s*(.+)/);
|
|
141
|
-
if (regionMatch)
|
|
143
|
+
if (regionMatch?.[1])
|
|
142
144
|
region = regionMatch[1].trim();
|
|
143
145
|
}
|
|
144
146
|
return { ...credentials, region };
|
|
@@ -587,7 +589,7 @@ async function ghCliAvailable() {
|
|
|
587
589
|
async function resolveSiteGithubSource(root) {
|
|
588
590
|
try {
|
|
589
591
|
const { execSync } = await import("node:child_process"), run = (cmd) => execSync(cmd, { cwd: root, stdio: ["ignore", "pipe", "ignore"] }).toString().trim(), match = run("git config --get remote.origin.url").match(/github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
590
|
-
if (!match)
|
|
592
|
+
if (!match?.[1])
|
|
591
593
|
return null;
|
|
592
594
|
return { repo: match[1], ref: run("git rev-parse HEAD") };
|
|
593
595
|
} catch {
|
|
@@ -824,7 +826,7 @@ function resolveMailboxes(mailboxes, domain) {
|
|
|
824
826
|
}
|
|
825
827
|
if (!raw || typeof raw !== "string")
|
|
826
828
|
continue;
|
|
827
|
-
const localPart = (raw.includes("@") ? raw.split("@")[0] : raw).trim();
|
|
829
|
+
const localPart = (raw.includes("@") ? raw.split("@")[0] ?? "" : raw).trim();
|
|
828
830
|
if (!localPart)
|
|
829
831
|
continue;
|
|
830
832
|
const address = `${localPart}@${domain}`, envPw = explicitPw || process.env[`MAIL_PASSWORD_${localPart.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`];
|
|
@@ -951,7 +953,7 @@ if [ "$ENV_CHANGED" = 1 ]; then systemctl restart mail 2>/dev/null || true; echo
|
|
|
951
953
|
input: script,
|
|
952
954
|
encoding: "utf8",
|
|
953
955
|
stdio: ["pipe", "pipe", "pipe"]
|
|
954
|
-
}), line = (out.match(/MAILTENANT:[^\n]*/) || [])[0] || "MAILTENANT:done", mailHost = (out.match(/MAILHOST:([^\n]*)/) || [])[1]?.trim() || `mail.${domain}`, dkimPubB64 = (out.match(/DKIMPUB:([^\n]*)/) || [])[1]?.trim() || void 0, madeAddrs = new Set([...out.matchAll(/MADE:([^\n]+)/g)].
|
|
956
|
+
}), line = (out.match(/MAILTENANT:[^\n]*/) || [])[0] || "MAILTENANT:done", mailHost = (out.match(/MAILHOST:([^\n]*)/) || [])[1]?.trim() || `mail.${domain}`, dkimPubB64 = (out.match(/DKIMPUB:([^\n]*)/) || [])[1]?.trim() || void 0, madeAddrs = new Set([...out.matchAll(/MADE:([^\n]+)/g)].flatMap((m) => m[1] ? [m[1].trim()] : [])), created = boxes.filter((b) => madeAddrs.has(b.address)).map((b) => ({ address: b.address, password: b.password }));
|
|
955
957
|
logger.success(`Mail routing reconciled (${line.replace("MAILTENANT:", "")})`);
|
|
956
958
|
if (created.length) {
|
|
957
959
|
logger.info(`Mail: created ${created.length} mailbox(es) \u2014 credentials below (save them; shown once):`);
|
|
@@ -1135,7 +1137,7 @@ export function deploy(buddy) {
|
|
|
1135
1137
|
const prodEnvPath = p.projectPath(".env.production");
|
|
1136
1138
|
if (existsSync(prodEnvPath)) {
|
|
1137
1139
|
const urlMatch = readFileSync(prodEnvPath, "utf-8").match(/^APP_URL=(.+)$/m);
|
|
1138
|
-
if (urlMatch) {
|
|
1140
|
+
if (urlMatch?.[1]) {
|
|
1139
1141
|
productionUrl = urlMatch[1].trim();
|
|
1140
1142
|
log.debug("Using APP_URL from .env.production:", productionUrl);
|
|
1141
1143
|
}
|
|
@@ -1390,6 +1392,7 @@ async function checkIfAwsIsBootstrapped(options) {
|
|
|
1390
1392
|
return !0;
|
|
1391
1393
|
}
|
|
1392
1394
|
} catch (error) {
|
|
1395
|
+
const caught = error && typeof error === "object" ? error : { message: String(error) };
|
|
1393
1396
|
log.debug(`Stack not found: ${getErrorMessage(error)}`);
|
|
1394
1397
|
}
|
|
1395
1398
|
if (!stackExists)
|
package/dist/commands/dev.js
CHANGED
|
@@ -242,7 +242,7 @@ export async function startDevelopmentServer(_options, _startTime) {
|
|
|
242
242
|
if (readyAnnounced)
|
|
243
243
|
return;
|
|
244
244
|
readyAnnounced = !0;
|
|
245
|
-
const failed = results.map((ok, i) => ok ? null : ports[i]
|
|
245
|
+
const failed = results.map((ok, i) => ok ? null : ports[i]?.name ?? null).filter((x) => x !== null);
|
|
246
246
|
let proxyReachable = !1;
|
|
247
247
|
if (hasCustomDomain && domain) {
|
|
248
248
|
(async () => {
|
|
@@ -443,19 +443,15 @@ async function importCraftSdk() {
|
|
|
443
443
|
const localCraftSdk = process.env.HOME ? `${process.env.HOME}/Code/Tools/craft/packages/typescript/src/index.ts` : void 0;
|
|
444
444
|
if (localCraftSdk && existsSync(localCraftSdk))
|
|
445
445
|
return await import(localCraftSdk);
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
446
|
+
const packageNames = ["craft-native", "@craft-native/craft", "@stacksjs/ts-craft"];
|
|
447
|
+
let primaryError;
|
|
448
|
+
for (const packageName of packageNames)
|
|
449
449
|
try {
|
|
450
|
-
return await import(
|
|
451
|
-
} catch {
|
|
452
|
-
|
|
453
|
-
return await import("@stacksjs/ts-craft");
|
|
454
|
-
} catch {
|
|
455
|
-
throw primaryError;
|
|
456
|
-
}
|
|
450
|
+
return await import(packageName);
|
|
451
|
+
} catch (error) {
|
|
452
|
+
primaryError ??= error;
|
|
457
453
|
}
|
|
458
|
-
|
|
454
|
+
throw primaryError;
|
|
459
455
|
}
|
|
460
456
|
async function cleanupStaleDevProcesses(ports) {
|
|
461
457
|
const projectRoot = projectPath(), actionDevPath = projectPath("storage/framework/core/actions/src/dev/"), pids = new Set;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -121,7 +121,10 @@ export function doctor(buddy) {
|
|
|
121
121
|
const skipped = result.skippedTables.length > 0 ? `, ${result.skippedTables.length} tables skipped (not migrated)` : "";
|
|
122
122
|
if (result.missing.length === 0)
|
|
123
123
|
return `${result.declared.length} declared unique constraints all indexed${skipped}`;
|
|
124
|
-
const sample = result.missing.slice(0, 5).map((u) => `${u.table}.${u.columns.join("+")}`).join(", "), more = result.missing.length > 5 ? ` (+${result.missing.length - 5} more)` : "", first = result.missing[0]
|
|
124
|
+
const sample = result.missing.slice(0, 5).map((u) => `${u.table}.${u.columns.join("+")}`).join(", "), more = result.missing.length > 5 ? ` (+${result.missing.length - 5} more)` : "", first = result.missing[0];
|
|
125
|
+
if (!first)
|
|
126
|
+
throw Error("Unique-index audit reported missing entries without details");
|
|
127
|
+
const example = `CREATE UNIQUE INDEX IF NOT EXISTS "${first.table}_${first.columns.join("_")}_unique" ON "${first.table}" ("${first.columns.join('", "')}")`;
|
|
125
128
|
throw Error(`${result.missing.length}/${result.declared.length} declared unique constraints have no UNIQUE index: ${sample}${more}. Run \`buddy migrate\` (re-queues missing unique-index migrations, #1952) \u2014 dedupe duplicate rows first or migrate hard-fails; if no migration file exists run \`buddy generate:migrations\`, or create manually: ${example}`);
|
|
126
129
|
}, 1e4);
|
|
127
130
|
await probe(checks, "FK orphans", async () => {
|
|
@@ -131,6 +134,8 @@ export function doctor(buddy) {
|
|
|
131
134
|
if (result.total === 0)
|
|
132
135
|
return "No orphan rows (PRAGMA foreign_key_check clean)";
|
|
133
136
|
const sample = result.orphans.slice(0, 5).map((o) => `${o.table}.${o.column} \u2192 ${o.parent} (${o.count})`).join(", "), more = result.orphans.length > 5 ? ` (+${result.orphans.length - 5} more)` : "", first = result.orphans[0];
|
|
137
|
+
if (!first)
|
|
138
|
+
throw Error("Foreign-key audit reported orphan rows without details");
|
|
134
139
|
throw Error(`${result.total} orphan rows violate FKs: ${sample}${more}. Legacy rows written under foreign_keys=OFF (#1951). Review and clean manually, e.g. DELETE FROM ${first.table} WHERE ${first.column} IS NOT NULL AND ${first.column} NOT IN (SELECT id FROM ${first.parent}) \u2014 doctor never deletes data.`);
|
|
135
140
|
}, 1e4);
|
|
136
141
|
await probe(checks, "Cache", async () => {
|
package/dist/commands/email.js
CHANGED
|
@@ -505,7 +505,7 @@ function parseRawEmailHeaders(rawEmail) {
|
|
|
505
505
|
const match = line.match(/^([^:]+):\s*(.*)$/);
|
|
506
506
|
if (match) {
|
|
507
507
|
currentKey = match[1].toLowerCase();
|
|
508
|
-
currentValue = match[2];
|
|
508
|
+
currentValue = match[2] ?? "";
|
|
509
509
|
headers[currentKey] = currentValue;
|
|
510
510
|
}
|
|
511
511
|
}
|
|
@@ -515,12 +515,10 @@ function parseRawEmailHeaders(rawEmail) {
|
|
|
515
515
|
function extractEmailAddress(str) {
|
|
516
516
|
if (!str)
|
|
517
517
|
return "";
|
|
518
|
-
|
|
519
|
-
return (match ? match[1] : str).toLowerCase().trim();
|
|
518
|
+
return (str.match(/<([^>]+)>/)?.[1] ?? str).toLowerCase().trim();
|
|
520
519
|
}
|
|
521
520
|
function extractEmailName(str) {
|
|
522
521
|
if (!str)
|
|
523
522
|
return "";
|
|
524
|
-
|
|
525
|
-
return match ? match[1].trim() : "";
|
|
523
|
+
return str.match(/^"?([^"<]+)"?\s*</)?.[1]?.trim() ?? "";
|
|
526
524
|
}
|
|
@@ -119,16 +119,20 @@ export declare const FEATURE_NAMES: readonly ['dashboard', 'commerce', 'cms', 'm
|
|
|
119
119
|
* manifest update — directory entries are recursive. Only add an entry
|
|
120
120
|
* when a feature introduces a new top-level path the framework didn't
|
|
121
121
|
* already claim.
|
|
122
|
+
* @defaultValue
|
|
123
|
+
* ```ts
|
|
124
|
+
* {
|
|
125
|
+
* cms: [ 'app/Actions/Cms/', 'app/Actions/Dashboard/Content/', 'app/Models/Content/', 'app/Models/Tag.ts', 'app/Models/Comment.ts', 'resources/views/dashboard/content/', ],
|
|
126
|
+
* commerce: [ 'app/Actions/Commerce/', 'app/Actions/Dashboard/Commerce/', 'app/Models/commerce/', 'resources/components/Dashboard/Commerce/', 'resources/views/dashboard/commerce/', ],
|
|
127
|
+
* dashboard: [ 'app/Actions/Dashboard/', 'resources/components/Dashboard/', 'resources/views/dashboard/', 'routes/dashboard.ts', 'routes/dashboard-api.ts', ],
|
|
128
|
+
* marketing: [ 'app/Actions/Dashboard/Marketing/', 'app/Models/Campaign.ts', 'app/Models/CampaignSend.ts', 'app/Models/EmailList.ts', 'app/Models/EmailListSubscriber.ts', 'app/Models/SocialPost.ts', 'resources/components/Marketing/', 'resources/views/dashboard/marketing/', ],
|
|
129
|
+
* monitoring: [ 'app/Actions/Monitoring/', 'app/Actions/TestErrorAction.ts', 'app/Models/Error.ts', 'functions/monitoring/', 'resources/views/dashboard/monitoring/', 'resources/views/dashboard/errors/', ],
|
|
130
|
+
* realtime: [ 'app/Actions/Realtime/', 'app/Actions/Dashboard/Realtime/', 'app/Models/realtime/', 'app/Broadcasts/', 'functions/realtime/', 'resources/views/dashboard/realtime/', ],
|
|
131
|
+
* queue: [ 'app/Actions/Queue/', 'app/Actions/Dashboard/Jobs/', 'app/Jobs/', 'app/Models/Job.ts', 'app/Models/FailedJob.ts', 'functions/jobs.ts', 'resources/views/dashboard/queue/', 'resources/views/dashboard/jobs/', ]
|
|
132
|
+
* }
|
|
133
|
+
* ```
|
|
122
134
|
*/
|
|
123
|
-
export declare const FEATURE_FILES:
|
|
124
|
-
cms: readonly ['app/Actions/Cms/', 'app/Actions/Dashboard/Content/', 'app/Models/Content/', 'app/Models/Tag.ts', 'app/Models/Comment.ts', 'resources/views/dashboard/content/'];
|
|
125
|
-
commerce: readonly ['app/Actions/Commerce/', 'app/Actions/Dashboard/Commerce/', 'app/Models/commerce/', 'resources/components/Dashboard/Commerce/', 'resources/views/dashboard/commerce/'];
|
|
126
|
-
dashboard: readonly ['app/Actions/Dashboard/', 'resources/components/Dashboard/', 'resources/views/dashboard/', 'routes/dashboard.ts', 'routes/dashboard-api.ts'];
|
|
127
|
-
marketing: readonly ['app/Actions/Dashboard/Marketing/', 'app/Models/Campaign.ts', 'app/Models/CampaignSend.ts', 'app/Models/EmailList.ts', 'app/Models/EmailListSubscriber.ts', 'app/Models/SocialPost.ts', 'resources/components/Marketing/', 'resources/views/dashboard/marketing/'];
|
|
128
|
-
monitoring: readonly ['app/Actions/Monitoring/', 'app/Actions/TestErrorAction.ts', 'app/Models/Error.ts', 'functions/monitoring/', 'resources/views/dashboard/monitoring/', 'resources/views/dashboard/errors/'];
|
|
129
|
-
realtime: readonly ['app/Actions/Realtime/', 'app/Actions/Dashboard/Realtime/', 'app/Models/realtime/', 'app/Broadcasts/', 'functions/realtime/', 'resources/views/dashboard/realtime/'];
|
|
130
|
-
queue: readonly ['app/Actions/Queue/', 'app/Actions/Dashboard/Jobs/', 'app/Jobs/', 'app/Models/Job.ts', 'app/Models/FailedJob.ts', 'functions/jobs.ts', 'resources/views/dashboard/queue/', 'resources/views/dashboard/jobs/']
|
|
131
|
-
};
|
|
135
|
+
export declare const FEATURE_FILES: Record<FeatureName, readonly string[]>;
|
|
132
136
|
/**
|
|
133
137
|
* Per-feature database table ownership (stacksjs/stacks#1854).
|
|
134
138
|
*
|
|
@@ -148,16 +152,20 @@ export declare const FEATURE_FILES: {
|
|
|
148
152
|
* across features (none today, but `categories` could end up here)
|
|
149
153
|
* should stay out of the manifest until that's resolved — the runner
|
|
150
154
|
* defaults to "run unless owned by a disabled feature".
|
|
155
|
+
* @defaultValue
|
|
156
|
+
* ```ts
|
|
157
|
+
* {
|
|
158
|
+
* cms: ['posts', 'pages', 'comments', 'tags', 'authors', 'categories'],
|
|
159
|
+
* commerce: [ 'products', 'product_variants', 'product_units', 'manufacturers', 'orders', 'order_items', 'carts', 'cart_items', 'payments', 'payment_methods', 'payment_products', 'payment_transactions', 'customers', 'subscribers', 'subscriber_emails', 'subscriptions', 'gift_cards', 'coupons', 'transactions', 'reviews', 'drivers', 'delivery_routes', 'digital_deliveries', 'shipping_methods', 'shipping_rates', 'shipping_zones', 'license_keys', 'loyalty_points', 'loyalty_rewards', 'print_devices', 'receipts', 'tax_rates', 'waitlist_products', 'waitlist_restaurants', ],
|
|
160
|
+
* dashboard: [ 'boards', 'board_columns', 'cards', 'card_labels', 'card_assignees', 'card_comments', 'labels', 'ci_run_states', 'ci_runner_samples', 'ci_runner_alert_states', 'requests', 'logs', ],
|
|
161
|
+
* marketing: [],
|
|
162
|
+
* monitoring: ['errors'],
|
|
163
|
+
* realtime: ['websockets'],
|
|
164
|
+
* queue: ['jobs', 'failed_jobs']
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
151
167
|
*/
|
|
152
|
-
export declare const FEATURE_TABLES:
|
|
153
|
-
cms: readonly ['posts', 'pages', 'comments', 'tags', 'authors', 'categories'];
|
|
154
|
-
commerce: readonly ['products', 'product_variants', 'product_units', 'manufacturers', 'orders', 'order_items', 'carts', 'cart_items', 'payments', 'payment_methods', 'payment_products', 'payment_transactions', 'customers', 'subscribers', 'subscriber_emails', 'subscriptions', 'gift_cards', 'coupons', 'transactions', 'reviews', 'drivers', 'delivery_routes', 'digital_deliveries', 'shipping_methods', 'shipping_rates', 'shipping_zones', 'license_keys', 'loyalty_points', 'loyalty_rewards', 'print_devices', 'receipts', 'tax_rates', 'waitlist_products', 'waitlist_restaurants'];
|
|
155
|
-
dashboard: readonly ['boards', 'board_columns', 'cards', 'card_labels', 'card_assignees', 'card_comments', 'labels', 'ci_run_states', 'ci_runner_samples', 'ci_runner_alert_states', 'requests', 'logs'];
|
|
156
|
-
marketing: never[];
|
|
157
|
-
monitoring: readonly ['errors'];
|
|
158
|
-
realtime: readonly ['websockets'];
|
|
159
|
-
queue: readonly ['jobs', 'failed_jobs']
|
|
160
|
-
};
|
|
168
|
+
export declare const FEATURE_TABLES: Record<FeatureName, readonly string[]>;
|
|
161
169
|
export declare interface CopyFeatureFilesOptions {
|
|
162
170
|
force?: boolean
|
|
163
171
|
source?: string
|
|
@@ -131,13 +131,13 @@ export const FEATURE_NAMES = [
|
|
|
131
131
|
export function migrationTable(filename) {
|
|
132
132
|
const inMatch = filename.match(/-in-([a-z0-9_]+)\.sql$/i);
|
|
133
133
|
if (inMatch)
|
|
134
|
-
return inMatch[1];
|
|
134
|
+
return inMatch[1] ?? null;
|
|
135
135
|
const createMatch = filename.match(/-create-([a-z0-9_]+)-table\.sql$/i);
|
|
136
136
|
if (createMatch)
|
|
137
|
-
return createMatch[1];
|
|
137
|
+
return createMatch[1] ?? null;
|
|
138
138
|
const alterMatch = filename.match(/-alter-([a-z0-9_]+)-/i);
|
|
139
139
|
if (alterMatch)
|
|
140
|
-
return alterMatch[1];
|
|
140
|
+
return alterMatch[1] ?? null;
|
|
141
141
|
return null;
|
|
142
142
|
}
|
|
143
143
|
export function migrationFeature(filename) {
|
package/dist/commands/http.js
CHANGED
|
@@ -11,7 +11,10 @@ export function http(buddy) {
|
|
|
11
11
|
};
|
|
12
12
|
buddy.command("http [domain]", descriptions.http).option("-p, --project [project]", descriptions.project, { default: !1 }).option("-v, --verbose", descriptions.verbose, { default: !1 }).action(async (domain, options) => {
|
|
13
13
|
log.debug("Running `buddy http [domain]` ...", options);
|
|
14
|
-
const url = domain || config.app.url
|
|
14
|
+
const url = domain || config.app.url;
|
|
15
|
+
if (!url)
|
|
16
|
+
throw Error("No domain configured. Pass a domain or set config.app.url.");
|
|
17
|
+
const client = new HttxClient({ verbose: options.verbose });
|
|
15
18
|
log.info(`GET ${url}`);
|
|
16
19
|
(await client.request(url.startsWith("http") ? url : `https://${url}`, {
|
|
17
20
|
method: "GET"
|
package/dist/commands/install.js
CHANGED
|
@@ -10,11 +10,10 @@ export function install(buddy) {
|
|
|
10
10
|
};
|
|
11
11
|
buddy.command("install", descriptions.install).option("-p, --project [project]", descriptions.project, { default: !1 }).option("-v, --verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
|
|
12
12
|
log.debug("Running `buddy install` ...", options);
|
|
13
|
-
|
|
13
|
+
if ((await runCommand("bun install", {
|
|
14
14
|
...options,
|
|
15
15
|
cwd: p.projectPath()
|
|
16
|
-
})
|
|
17
|
-
if (result && typeof result === "object" && "isErr" in result && result.isErr?.()) {
|
|
16
|
+
})).isErr) {
|
|
18
17
|
log.error("bun install failed");
|
|
19
18
|
process.exit(ExitCode.FatalError);
|
|
20
19
|
}
|
package/dist/commands/list.js
CHANGED
|
@@ -55,7 +55,7 @@ export function list(buddy) {
|
|
|
55
55
|
continue;
|
|
56
56
|
let group = "General";
|
|
57
57
|
if (name.includes(":"))
|
|
58
|
-
group = name.split(":")[0];
|
|
58
|
+
group = name.split(":")[0] ?? "General";
|
|
59
59
|
else if (["dev", "build", "test", "lint"].includes(name))
|
|
60
60
|
group = "Development";
|
|
61
61
|
else if (["deploy", "release", "publish"].includes(name))
|
package/dist/commands/make.js
CHANGED
package/dist/commands/publish.js
CHANGED
|
@@ -157,7 +157,10 @@ async function publishResource(ctx) {
|
|
|
157
157
|
for (const m of matches)
|
|
158
158
|
log.info(` ${m}`);
|
|
159
159
|
}
|
|
160
|
-
const sourcePath = matches[0]
|
|
160
|
+
const sourcePath = matches[0];
|
|
161
|
+
if (!sourcePath)
|
|
162
|
+
throw Error(`Could not resolve default ${kind}: ${fileName}`);
|
|
163
|
+
const targetPath = `${userDir.replace(/\/$/, "")}/${fileName}`;
|
|
161
164
|
if (existsSync(targetPath) && !force) {
|
|
162
165
|
log.error(`Already exists: ${italic(targetPath)}`);
|
|
163
166
|
log.info("Pass --force to overwrite.");
|
package/dist/commands/serve.js
CHANGED
|
@@ -51,7 +51,8 @@ export function serve(buddy) {
|
|
|
51
51
|
break;
|
|
52
52
|
}
|
|
53
53
|
} catch {}
|
|
54
|
-
(
|
|
54
|
+
if (!stxServe)
|
|
55
|
+
({ serve: stxServe } = await import("bun-plugin-stx/serve"));
|
|
55
56
|
const stxModule = await resolveVendoredStxModule(), { site: siteConfig, i18n: i18nConfig } = await loadStxSiteConfig(), userViewsPath = "resources/views", defaultsResources = resolveDefaultsResources(), defaultViewsPath = join(defaultsResources, "views"), userLayoutsPath = existsSync("resources/views/layouts") ? "resources/views/layouts" : "resources/layouts", userComponentsPath = existsSync("resources/views/components") ? "resources/views/components" : "resources/components", apiBase = process.env.API_URL || `http://127.0.0.1:${Number(process.env.PORT_API) || config.ports?.api || 3008}`;
|
|
56
57
|
log.info(`Starting production server on port ${port}...`);
|
|
57
58
|
await stxServe({
|
package/dist/commands/share.js
CHANGED
|
@@ -119,12 +119,16 @@ export function share(buddy) {
|
|
|
119
119
|
companion.runner({ verbose: options.verbose ?? !1 }).catch(() => {});
|
|
120
120
|
const results = await Promise.allSettled(companions.map((c) => waitForPort(c.port, "localhost", 60000)));
|
|
121
121
|
unmuteOutput();
|
|
122
|
-
for (let i = 0;i < companions.length; i++)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
for (let i = 0;i < companions.length; i++) {
|
|
123
|
+
const result = results[i], companion = companions[i];
|
|
124
|
+
if (!result || !companion)
|
|
125
|
+
continue;
|
|
126
|
+
if (result.status === "fulfilled") {
|
|
127
|
+
startedCompanions.push(companion);
|
|
128
|
+
s.succeed(`${bold(companion.label)} ready ${dim(`on :${companion.port}`)}`);
|
|
126
129
|
} else
|
|
127
|
-
s.fail(`${
|
|
130
|
+
s.fail(`${companion.label} failed to start ${dim(`on :${companion.port}`)}`);
|
|
131
|
+
}
|
|
128
132
|
}
|
|
129
133
|
console.log();
|
|
130
134
|
s.start("Creating tunnel...");
|
|
@@ -193,13 +197,14 @@ export function share(buddy) {
|
|
|
193
197
|
} catch (error) {
|
|
194
198
|
unmuteOutput();
|
|
195
199
|
s.fail(getErrorMessage(error));
|
|
196
|
-
|
|
200
|
+
const caught = error instanceof Error ? error : Error(String(error));
|
|
201
|
+
if (caught.message.includes("timeout") || caught.message.includes("ECONNREFUSED")) {
|
|
197
202
|
log.error(`Could not reach tunnel server at ${server}`);
|
|
198
203
|
log.info(`Verify with: curl -sk https://${server}/status`);
|
|
199
204
|
} else
|
|
200
|
-
log.error(`Failed to create tunnel: ${
|
|
205
|
+
log.error(`Failed to create tunnel: ${caught.message}`);
|
|
201
206
|
if (options.verbose)
|
|
202
|
-
log.error(
|
|
207
|
+
log.error(caught.stack);
|
|
203
208
|
for (const t of tunnels)
|
|
204
209
|
t.close();
|
|
205
210
|
await outro("Share failed", { startTime: perf, useSeconds: !0 });
|
package/dist/config.js
CHANGED
|
@@ -153,7 +153,7 @@ export async function loadBuddyConfig() {
|
|
|
153
153
|
throw Error("Configuration validation failed");
|
|
154
154
|
}
|
|
155
155
|
cachedConfig = config;
|
|
156
|
-
return
|
|
156
|
+
return config;
|
|
157
157
|
} catch (error) {
|
|
158
158
|
log.warn(`Failed to load buddy config from ${configPath}:`, error);
|
|
159
159
|
}
|
package/dist/custom-cli.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
export {};
|
|
@@ -6,7 +6,4 @@ export declare function runMigrator(req: MigrateProjectRequest): Promise<Migrati
|
|
|
6
6
|
* writing to `MIGRATION_REPORT.md` in the target project.
|
|
7
7
|
*/
|
|
8
8
|
export declare function renderReport(report: MigrationReport): string;
|
|
9
|
-
export declare const DRIVERS:
|
|
10
|
-
laravel: unknown;
|
|
11
|
-
rails: unknown
|
|
12
|
-
};
|
|
9
|
+
export declare const DRIVERS: Record<string, Driver>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export function parseLaravelMigration(source) {
|
|
2
2
|
const createMatch = source.match(/Schema::create\(\s*['"]([a-z0-9_]+)['"]\s*,\s*function\s*\([^)]*\)\s*\{([\s\S]*?)\}\s*\)\s*;/i);
|
|
3
|
-
if (!createMatch)
|
|
3
|
+
if (!createMatch?.[1] || createMatch[2] === void 0)
|
|
4
4
|
return null;
|
|
5
5
|
const table = createMatch[1], body = createMatch[2], skipped = [], columns = [], indexes = [], lines = body.split(`
|
|
6
6
|
`).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("//") && !line.startsWith("*") && !line.startsWith("/*"));
|
|
@@ -29,7 +29,7 @@ export function parseLaravelMigration(source) {
|
|
|
29
29
|
}
|
|
30
30
|
function parseColumnLine(line) {
|
|
31
31
|
const methodMatch = line.match(/^\$table->([a-zA-Z_]+)\s*\(([^)]*)\)(.*)$/);
|
|
32
|
-
if (!methodMatch)
|
|
32
|
+
if (!methodMatch?.[1] || methodMatch[2] === void 0 || methodMatch[3] === void 0)
|
|
33
33
|
return null;
|
|
34
34
|
const method = methodMatch[1], args = methodMatch[2].trim(), rest = methodMatch[3];
|
|
35
35
|
if (method === "timestamps")
|
|
@@ -98,7 +98,9 @@ function applyModifiers(rest, column) {
|
|
|
98
98
|
const modRegex = /->([a-zA-Z_]+)\s*\(([^)]*)\)/g;
|
|
99
99
|
let match;
|
|
100
100
|
while ((match = modRegex.exec(rest)) !== null) {
|
|
101
|
-
const mod = match[1], args = match[2]
|
|
101
|
+
const mod = match[1], args = match[2]?.trim() ?? "";
|
|
102
|
+
if (!mod)
|
|
103
|
+
continue;
|
|
102
104
|
switch (mod) {
|
|
103
105
|
case "nullable":
|
|
104
106
|
column.nullable = !0;
|
|
@@ -129,21 +131,20 @@ function applyModifiers(rest, column) {
|
|
|
129
131
|
}
|
|
130
132
|
}
|
|
131
133
|
function parseStringArg(args) {
|
|
132
|
-
|
|
133
|
-
return m ? m[1] : null;
|
|
134
|
+
return args.match(/^['"]([^'"]+)['"]/)?.[1] ?? null;
|
|
134
135
|
}
|
|
135
136
|
function parseIndexArg(args) {
|
|
136
137
|
const single = parseStringArg(args);
|
|
137
138
|
if (single)
|
|
138
139
|
return [single];
|
|
139
140
|
const arr = args.match(/\[([^\]]*)\]/);
|
|
140
|
-
if (!arr)
|
|
141
|
+
if (!arr?.[1])
|
|
141
142
|
return [];
|
|
142
143
|
return arr[1].split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
143
144
|
}
|
|
144
145
|
function parseDefaultArg(args) {
|
|
145
146
|
const stringMatch = args.match(/^['"]([^'"]*)['"]/);
|
|
146
|
-
if (stringMatch)
|
|
147
|
+
if (stringMatch?.[1] !== void 0)
|
|
147
148
|
return `'${stringMatch[1].replace(/'/g, "''")}'`;
|
|
148
149
|
const trimmed = args.trim();
|
|
149
150
|
if (trimmed === "true")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const RELATION_KINDS = ["belongsTo", "hasMany", "hasOne", "belongsToMany"];
|
|
2
2
|
export function parseLaravelModel(source) {
|
|
3
3
|
const classMatch = source.match(/\bclass\s+([A-Z][A-Za-z0-9_]*)\b/);
|
|
4
|
-
if (!classMatch)
|
|
4
|
+
if (!classMatch?.[1])
|
|
5
5
|
return null;
|
|
6
6
|
const className = classMatch[1], table = extractStringProperty(source, "table") ?? snakeCasePlural(className), fillable = extractArrayProperty(source, "fillable"), hidden = extractArrayProperty(source, "hidden"), casts = extractKeyedArrayProperty(source, "casts"), relationships = extractRelationships(source), notes = [];
|
|
7
7
|
if (/protected\s+\$appends\s*=/.test(source))
|
|
@@ -16,23 +16,23 @@ export function parseLaravelModel(source) {
|
|
|
16
16
|
return { className, table, fillable, hidden, casts, relationships, tsSource, notes };
|
|
17
17
|
}
|
|
18
18
|
function extractStringProperty(source, name) {
|
|
19
|
-
const re = new RegExp(`protected\\s+\\$${name}\\s*=\\s*['"]([^'"]+)['"]`)
|
|
20
|
-
return
|
|
19
|
+
const re = new RegExp(`protected\\s+\\$${name}\\s*=\\s*['"]([^'"]+)['"]`);
|
|
20
|
+
return source.match(re)?.[1] ?? null;
|
|
21
21
|
}
|
|
22
22
|
function extractArrayProperty(source, name) {
|
|
23
23
|
const re = new RegExp(`protected\\s+\\$${name}\\s*=\\s*\\[([^\\]]*)\\]`, "s"), m = source.match(re);
|
|
24
|
-
if (!m)
|
|
24
|
+
if (!m?.[1])
|
|
25
25
|
return [];
|
|
26
26
|
return m[1].split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
27
27
|
}
|
|
28
28
|
function extractKeyedArrayProperty(source, name) {
|
|
29
29
|
const re = new RegExp(`protected\\s+\\$${name}\\s*=\\s*\\[([^\\]]*)\\]`, "s"), m = source.match(re);
|
|
30
|
-
if (!m)
|
|
30
|
+
if (!m?.[1])
|
|
31
31
|
return {};
|
|
32
32
|
const out = {}, pairs = m[1].split(",");
|
|
33
33
|
for (const pair of pairs) {
|
|
34
34
|
const kv = pair.match(/['"]([^'"]+)['"]\s*=>\s*['"]([^'"]+)['"]/);
|
|
35
|
-
if (kv)
|
|
35
|
+
if (kv?.[1] && kv[2] !== void 0)
|
|
36
36
|
out[kv[1]] = kv[2];
|
|
37
37
|
}
|
|
38
38
|
return out;
|
|
@@ -42,6 +42,8 @@ function extractRelationships(source) {
|
|
|
42
42
|
let m;
|
|
43
43
|
while ((m = methodRegex.exec(source)) !== null) {
|
|
44
44
|
const name = m[1], kind = m[2], target = m[3];
|
|
45
|
+
if (!name || !kind || !target)
|
|
46
|
+
continue;
|
|
45
47
|
found.push({ name, kind, target });
|
|
46
48
|
}
|
|
47
49
|
return found;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/buddy",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.70.
|
|
4
|
+
"version": "0.70.74",
|
|
5
5
|
"description": "Meet Buddy. The Stacks runtime.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -83,11 +83,11 @@
|
|
|
83
83
|
"compile:darwin-arm64": "bun build ./src/cli.ts --compile --minify --target=bun-darwin-arm64 --external=localtunnels/cloud --external=bun-queue --outfile bin/buddy-darwin-arm64",
|
|
84
84
|
"typecheck": "bun tsc --noEmit",
|
|
85
85
|
"zip:all": "bun run zip:linux-x64 && bun run zip:linux-arm64 && bun run zip:windows-x64 && bun run zip:darwin-x64 && bun run zip:darwin-arm64",
|
|
86
|
-
"zip:linux-x64": "zip -
|
|
87
|
-
"zip:linux-arm64": "zip -
|
|
88
|
-
"zip:windows-x64": "zip -
|
|
89
|
-
"zip:darwin-x64": "zip -
|
|
90
|
-
"zip:darwin-arm64": "zip -
|
|
86
|
+
"zip:linux-x64": "zip -j bin/buddy-linux-x64.zip bin/buddy-linux-x64",
|
|
87
|
+
"zip:linux-arm64": "zip -j bin/buddy-linux-arm64.zip bin/buddy-linux-arm64",
|
|
88
|
+
"zip:windows-x64": "zip -j bin/buddy-windows-x64.zip bin/buddy-windows-x64.exe",
|
|
89
|
+
"zip:darwin-x64": "zip -j bin/buddy-darwin-x64.zip bin/buddy-darwin-x64",
|
|
90
|
+
"zip:darwin-arm64": "zip -j bin/buddy-darwin-arm64.zip bin/buddy-darwin-arm64",
|
|
91
91
|
"prepublishOnly": "bun run build"
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
@@ -135,10 +135,10 @@
|
|
|
135
135
|
"@stacksjs/ui": "^0.70.23",
|
|
136
136
|
"@stacksjs/utils": "^0.70.23",
|
|
137
137
|
"@stacksjs/validation": "^0.70.23",
|
|
138
|
-
"@stacksjs/ts-cloud": "^0.7.
|
|
138
|
+
"@stacksjs/ts-cloud": "^0.7.14"
|
|
139
139
|
},
|
|
140
140
|
"devDependencies": {
|
|
141
|
-
"better-dx": "^0.2.
|
|
141
|
+
"better-dx": "^0.2.16"
|
|
142
142
|
},
|
|
143
143
|
"web-types": "./web-types.json"
|
|
144
144
|
}
|