@specific.dev/cli 0.1.186 → 0.1.188
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/admin/404/index.html +1 -1
- package/dist/admin/404.html +1 -1
- package/dist/admin/__next.!KGRlZmF1bHQp.__PAGE__.txt +2 -2
- package/dist/admin/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/__next._full.txt +2 -2
- package/dist/admin/__next._head.txt +1 -1
- package/dist/admin/__next._index.txt +1 -1
- package/dist/admin/__next._tree.txt +1 -1
- package/dist/admin/_next/static/chunks/{cbb47779f2b2b95e.js → 5988e82f48887bfd.js} +2 -2
- package/dist/admin/_not-found/__next._full.txt +1 -1
- package/dist/admin/_not-found/__next._head.txt +1 -1
- package/dist/admin/_not-found/__next._index.txt +1 -1
- package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
- package/dist/admin/_not-found/__next._not-found.txt +1 -1
- package/dist/admin/_not-found/__next._tree.txt +1 -1
- package/dist/admin/_not-found/index.html +1 -1
- package/dist/admin/_not-found/index.txt +1 -1
- package/dist/admin/crons/__next.!KGRlZmF1bHQp.crons.__PAGE__.txt +2 -2
- package/dist/admin/crons/__next.!KGRlZmF1bHQp.crons.txt +1 -1
- package/dist/admin/crons/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/crons/__next._full.txt +2 -2
- package/dist/admin/crons/__next._head.txt +1 -1
- package/dist/admin/crons/__next._index.txt +1 -1
- package/dist/admin/crons/__next._tree.txt +1 -1
- package/dist/admin/crons/index.html +1 -1
- package/dist/admin/crons/index.txt +2 -2
- package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +1 -1
- package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
- package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/databases/__next._full.txt +1 -1
- package/dist/admin/databases/__next._head.txt +1 -1
- package/dist/admin/databases/__next._index.txt +1 -1
- package/dist/admin/databases/__next._tree.txt +1 -1
- package/dist/admin/databases/index.html +1 -1
- package/dist/admin/databases/index.txt +1 -1
- package/dist/admin/fullscreen/__next._full.txt +2 -2
- package/dist/admin/fullscreen/__next._head.txt +1 -1
- package/dist/admin/fullscreen/__next._index.txt +1 -1
- package/dist/admin/fullscreen/__next._tree.txt +1 -1
- package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +2 -2
- package/dist/admin/fullscreen/__next.fullscreen.txt +1 -1
- package/dist/admin/fullscreen/databases/__next._full.txt +1 -1
- package/dist/admin/fullscreen/databases/__next._head.txt +1 -1
- package/dist/admin/fullscreen/databases/__next._index.txt +1 -1
- package/dist/admin/fullscreen/databases/__next._tree.txt +1 -1
- package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +1 -1
- package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +1 -1
- package/dist/admin/fullscreen/databases/__next.fullscreen.txt +1 -1
- package/dist/admin/fullscreen/databases/index.html +1 -1
- package/dist/admin/fullscreen/databases/index.txt +1 -1
- package/dist/admin/fullscreen/index.html +1 -1
- package/dist/admin/fullscreen/index.txt +2 -2
- package/dist/admin/index.html +1 -1
- package/dist/admin/index.txt +2 -2
- package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +1 -1
- package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +1 -1
- package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/mail/__next._full.txt +1 -1
- package/dist/admin/mail/__next._head.txt +1 -1
- package/dist/admin/mail/__next._index.txt +1 -1
- package/dist/admin/mail/__next._tree.txt +1 -1
- package/dist/admin/mail/index.html +1 -1
- package/dist/admin/mail/index.txt +1 -1
- package/dist/admin/services/__next.!KGRlZmF1bHQp.services.__PAGE__.txt +1 -1
- package/dist/admin/services/__next.!KGRlZmF1bHQp.services.txt +1 -1
- package/dist/admin/services/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/services/__next._full.txt +1 -1
- package/dist/admin/services/__next._head.txt +1 -1
- package/dist/admin/services/__next._index.txt +1 -1
- package/dist/admin/services/__next._tree.txt +1 -1
- package/dist/admin/services/index.html +1 -1
- package/dist/admin/services/index.txt +1 -1
- package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.__PAGE__.txt +1 -1
- package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.txt +1 -1
- package/dist/admin/storage/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/storage/__next._full.txt +1 -1
- package/dist/admin/storage/__next._head.txt +1 -1
- package/dist/admin/storage/__next._index.txt +1 -1
- package/dist/admin/storage/__next._tree.txt +1 -1
- package/dist/admin/storage/index.html +1 -1
- package/dist/admin/storage/index.txt +1 -1
- package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +1 -1
- package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +1 -1
- package/dist/admin/workflows/__next._full.txt +1 -1
- package/dist/admin/workflows/__next._head.txt +1 -1
- package/dist/admin/workflows/__next._index.txt +1 -1
- package/dist/admin/workflows/__next._tree.txt +1 -1
- package/dist/admin/workflows/index.html +1 -1
- package/dist/admin/workflows/index.txt +1 -1
- package/dist/cli.js +322 -69
- package/dist/docs/storage.md +30 -0
- package/package.json +1 -1
- /package/dist/admin/_next/static/{6oKX20WXYrqT7PTdniaYQ → 3XymYpBfQIIiwwuOTHt6x}/_buildManifest.js +0 -0
- /package/dist/admin/_next/static/{6oKX20WXYrqT7PTdniaYQ → 3XymYpBfQIIiwwuOTHt6x}/_clientMiddlewareManifest.json +0 -0
- /package/dist/admin/_next/static/{6oKX20WXYrqT7PTdniaYQ → 3XymYpBfQIIiwwuOTHt6x}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -368324,7 +368324,7 @@ function parseReferenceString(str) {
|
|
|
368324
368324
|
const storageMatch = str.match(new RegExp(`^storage\\.(${id})\\.(\\w+)$`));
|
|
368325
368325
|
if (storageMatch && storageMatch[1] && storageMatch[2]) {
|
|
368326
368326
|
const attr = storageMatch[2];
|
|
368327
|
-
if (["endpoint", "access_key", "secret_key", "bucket"].includes(attr)) {
|
|
368327
|
+
if (["endpoint", "access_key", "secret_key", "bucket", "public_url"].includes(attr)) {
|
|
368328
368328
|
return {
|
|
368329
368329
|
type: "storage",
|
|
368330
368330
|
name: storageMatch[1],
|
|
@@ -368860,8 +368860,28 @@ function parseStorage(data) {
|
|
|
368860
368860
|
return [];
|
|
368861
368861
|
}
|
|
368862
368862
|
const result = [];
|
|
368863
|
-
for (const [name] of Object.entries(data)) {
|
|
368864
|
-
|
|
368863
|
+
for (const [name, fieldsArray] of Object.entries(data)) {
|
|
368864
|
+
if (Array.isArray(fieldsArray) && fieldsArray.length > 1) {
|
|
368865
|
+
throw new Error(`Storage "${name}" is defined more than once. Merge the blocks into a single storage "${name}" block.`);
|
|
368866
|
+
}
|
|
368867
|
+
const fields = Array.isArray(fieldsArray) ? fieldsArray[0] : fieldsArray;
|
|
368868
|
+
if (!fields || typeof fields !== "object" || Array.isArray(fields)) {
|
|
368869
|
+
result.push({ name });
|
|
368870
|
+
continue;
|
|
368871
|
+
}
|
|
368872
|
+
const fieldObj = fields;
|
|
368873
|
+
const unknownFields = Object.keys(fieldObj).filter((field) => field !== "public");
|
|
368874
|
+
if (unknownFields.length > 0) {
|
|
368875
|
+
throw new Error(`Storage "${name}" has unsupported field "${unknownFields[0]}". Supported fields: public.`);
|
|
368876
|
+
}
|
|
368877
|
+
const publicValue = Array.isArray(fieldObj.public) ? fieldObj.public[0] : fieldObj.public;
|
|
368878
|
+
if (publicValue !== void 0 && typeof publicValue !== "boolean") {
|
|
368879
|
+
throw new Error(`Storage "${name}" has invalid "public" value. Must be a boolean.`);
|
|
368880
|
+
}
|
|
368881
|
+
result.push({
|
|
368882
|
+
name,
|
|
368883
|
+
...publicValue !== void 0 ? { public: publicValue } : {}
|
|
368884
|
+
});
|
|
368865
368885
|
}
|
|
368866
368886
|
return result;
|
|
368867
368887
|
}
|
|
@@ -369021,82 +369041,127 @@ function validateEndpointReferences(config) {
|
|
|
369021
369041
|
for (const service of config.services) {
|
|
369022
369042
|
serviceEndpointsMap.set(service.name, service.endpoints);
|
|
369023
369043
|
}
|
|
369044
|
+
const storageByName = new Map(config.storage.map((storage) => [storage.name, storage]));
|
|
369045
|
+
const validateStorageReference = (ownerType, ownerName, key, ref, envLabel2 = "env var") => {
|
|
369046
|
+
const target = storageByName.get(ref.name);
|
|
369047
|
+
if (!target) {
|
|
369048
|
+
errors.push({
|
|
369049
|
+
service: ownerName,
|
|
369050
|
+
message: `${ownerType} "${ownerName}" references unknown storage "${ref.name}" in ${envLabel2} "${key}".`
|
|
369051
|
+
});
|
|
369052
|
+
return;
|
|
369053
|
+
}
|
|
369054
|
+
if (ref.attribute === "public_url" && target.public !== true) {
|
|
369055
|
+
errors.push({
|
|
369056
|
+
service: ownerName,
|
|
369057
|
+
message: `${ownerType} "${ownerName}" references public_url of storage "${ref.name}" in ${envLabel2} "${key}", but that storage is not public. Add \`public = true\` to the storage block.`
|
|
369058
|
+
});
|
|
369059
|
+
}
|
|
369060
|
+
};
|
|
369024
369061
|
for (const service of config.services) {
|
|
369025
|
-
if (
|
|
369026
|
-
|
|
369027
|
-
|
|
369028
|
-
|
|
369029
|
-
|
|
369030
|
-
|
|
369031
|
-
|
|
369032
|
-
|
|
369033
|
-
message: `Service "${service.name}" has multiple endpoints but uses \${port} for env var "${key}". Use \${endpoint.<name>.port} instead.`
|
|
369034
|
-
});
|
|
369035
|
-
}
|
|
369036
|
-
if (ref.type === "service") {
|
|
369037
|
-
const serviceRef = ref;
|
|
369038
|
-
const targetEndpoints = serviceEndpointsMap.get(serviceRef.serviceName);
|
|
369039
|
-
if (!targetEndpoints) {
|
|
369040
|
-
errors.push({
|
|
369041
|
-
service: service.name,
|
|
369042
|
-
message: `Service "${service.name}" references unknown service "${serviceRef.serviceName}" in env var "${key}".`
|
|
369043
|
-
});
|
|
369044
|
-
continue;
|
|
369045
|
-
}
|
|
369046
|
-
if (targetEndpoints.length > 1 && !serviceRef.endpointName) {
|
|
369062
|
+
if (service.env) {
|
|
369063
|
+
for (const [key, value] of Object.entries(service.env)) {
|
|
369064
|
+
const refs = extractEnvReferences(value);
|
|
369065
|
+
for (const ref of refs) {
|
|
369066
|
+
if (ref.type === "storage") {
|
|
369067
|
+
validateStorageReference("Service", service.name, key, ref);
|
|
369068
|
+
}
|
|
369069
|
+
if (service.endpoints.length > 1 && ref.type === "port") {
|
|
369047
369070
|
errors.push({
|
|
369048
369071
|
service: service.name,
|
|
369049
|
-
message: `Service "${service.name}"
|
|
369072
|
+
message: `Service "${service.name}" has multiple endpoints but uses \${port} for env var "${key}". Use \${endpoint.<name>.port} instead.`
|
|
369050
369073
|
});
|
|
369051
369074
|
}
|
|
369052
|
-
if (
|
|
369053
|
-
const
|
|
369054
|
-
|
|
369075
|
+
if (ref.type === "service") {
|
|
369076
|
+
const serviceRef = ref;
|
|
369077
|
+
const targetEndpoints = serviceEndpointsMap.get(serviceRef.serviceName);
|
|
369078
|
+
if (!targetEndpoints) {
|
|
369055
369079
|
errors.push({
|
|
369056
369080
|
service: service.name,
|
|
369057
|
-
message: `Service "${service.name}" references
|
|
369081
|
+
message: `Service "${service.name}" references unknown service "${serviceRef.serviceName}" in env var "${key}".`
|
|
369058
369082
|
});
|
|
369083
|
+
continue;
|
|
369059
369084
|
}
|
|
369060
|
-
|
|
369061
|
-
|
|
369062
|
-
|
|
369063
|
-
|
|
369064
|
-
|
|
369065
|
-
|
|
369085
|
+
if (targetEndpoints.length > 1 && !serviceRef.endpointName) {
|
|
369086
|
+
errors.push({
|
|
369087
|
+
service: service.name,
|
|
369088
|
+
message: `Service "${service.name}" references service "${serviceRef.serviceName}" which has multiple endpoints, but no endpoint name specified in env var "${key}". Use \${service.${serviceRef.serviceName}.endpoint.<name>.${serviceRef.attribute}} instead.`
|
|
369089
|
+
});
|
|
369090
|
+
}
|
|
369091
|
+
if (serviceRef.endpointName) {
|
|
369092
|
+
const endpointExists = targetEndpoints.some((e) => e.name === serviceRef.endpointName);
|
|
369093
|
+
if (!endpointExists) {
|
|
369066
369094
|
errors.push({
|
|
369067
369095
|
service: service.name,
|
|
369068
|
-
message: `Service "${service.name}" references
|
|
369096
|
+
message: `Service "${service.name}" references endpoint "${serviceRef.endpointName}" on service "${serviceRef.serviceName}" in env var "${key}", but that endpoint does not exist.`
|
|
369069
369097
|
});
|
|
369070
369098
|
}
|
|
369071
|
-
}
|
|
369072
|
-
|
|
369073
|
-
|
|
369074
|
-
|
|
369075
|
-
|
|
369076
|
-
|
|
369077
|
-
|
|
369099
|
+
}
|
|
369100
|
+
if (serviceRef.attribute === "public_url") {
|
|
369101
|
+
const endpointName = serviceRef.endpointName;
|
|
369102
|
+
if (endpointName) {
|
|
369103
|
+
const targetEndpoint = targetEndpoints.find((e) => e.name === endpointName);
|
|
369104
|
+
if (targetEndpoint && !targetEndpoint.public) {
|
|
369105
|
+
errors.push({
|
|
369106
|
+
service: service.name,
|
|
369107
|
+
message: `Service "${service.name}" references public_url of endpoint "${endpointName}" on service "${serviceRef.serviceName}" in env var "${key}", but that endpoint is not public. Add \`public = true\` to the endpoint.`
|
|
369108
|
+
});
|
|
369109
|
+
}
|
|
369110
|
+
} else {
|
|
369111
|
+
const defaultEndpoint = targetEndpoints.length === 1 ? targetEndpoints[0] : targetEndpoints.find((e) => e.name === "default");
|
|
369112
|
+
if (defaultEndpoint && !defaultEndpoint.public) {
|
|
369113
|
+
errors.push({
|
|
369114
|
+
service: service.name,
|
|
369115
|
+
message: `Service "${service.name}" references public_url of service "${serviceRef.serviceName}" in env var "${key}", but its endpoint is not public. Add \`public = true\` to the endpoint.`
|
|
369116
|
+
});
|
|
369117
|
+
}
|
|
369078
369118
|
}
|
|
369079
369119
|
}
|
|
369080
369120
|
}
|
|
369121
|
+
if (ref.type === "endpoint") {
|
|
369122
|
+
const endpointRef = ref;
|
|
369123
|
+
const endpointExists = service.endpoints.some((e) => e.name === endpointRef.endpointName);
|
|
369124
|
+
if (!endpointExists) {
|
|
369125
|
+
errors.push({
|
|
369126
|
+
service: service.name,
|
|
369127
|
+
message: `Service "${service.name}" references endpoint "${endpointRef.endpointName}" in env var "${key}", but that endpoint is not defined on this service.`
|
|
369128
|
+
});
|
|
369129
|
+
}
|
|
369130
|
+
}
|
|
369131
|
+
if (ref.type === "volume") {
|
|
369132
|
+
const volumeRef = ref;
|
|
369133
|
+
const volumeExists = service.volumes?.some((v) => v.name === volumeRef.volumeName);
|
|
369134
|
+
if (!volumeExists) {
|
|
369135
|
+
errors.push({
|
|
369136
|
+
service: service.name,
|
|
369137
|
+
message: `Service "${service.name}" references volume "${volumeRef.volumeName}" in env var "${key}", but that volume is not defined on this service.`
|
|
369138
|
+
});
|
|
369139
|
+
}
|
|
369140
|
+
}
|
|
369081
369141
|
}
|
|
369082
|
-
|
|
369083
|
-
|
|
369084
|
-
|
|
369085
|
-
|
|
369086
|
-
|
|
369087
|
-
|
|
369088
|
-
|
|
369089
|
-
});
|
|
369142
|
+
}
|
|
369143
|
+
}
|
|
369144
|
+
if (service.dev?.env) {
|
|
369145
|
+
for (const [key, value] of Object.entries(service.dev.env)) {
|
|
369146
|
+
for (const ref of extractEnvReferences(value)) {
|
|
369147
|
+
if (ref.type === "storage") {
|
|
369148
|
+
validateStorageReference("Service", service.name, key, ref, "dev env var");
|
|
369090
369149
|
}
|
|
369091
369150
|
}
|
|
369092
|
-
|
|
369093
|
-
|
|
369094
|
-
|
|
369095
|
-
|
|
369096
|
-
|
|
369097
|
-
|
|
369098
|
-
|
|
369099
|
-
|
|
369151
|
+
}
|
|
369152
|
+
}
|
|
369153
|
+
}
|
|
369154
|
+
for (const cron of config.crons) {
|
|
369155
|
+
for (const [envLabel2, env2] of [
|
|
369156
|
+
["env var", cron.env],
|
|
369157
|
+
["dev env var", cron.dev?.env]
|
|
369158
|
+
]) {
|
|
369159
|
+
if (!env2)
|
|
369160
|
+
continue;
|
|
369161
|
+
for (const [key, value] of Object.entries(env2)) {
|
|
369162
|
+
for (const ref of extractEnvReferences(value)) {
|
|
369163
|
+
if (ref.type === "storage") {
|
|
369164
|
+
validateStorageReference("Cron", cron.name, key, ref, envLabel2);
|
|
369100
369165
|
}
|
|
369101
369166
|
}
|
|
369102
369167
|
}
|
|
@@ -369945,6 +370010,7 @@ async function startStorage(storage, port, dataDir) {
|
|
|
369945
370010
|
accessKey,
|
|
369946
370011
|
secretKey,
|
|
369947
370012
|
bucket: storage.name,
|
|
370013
|
+
...storage.public ? { publicUrl: `${endpoint}/${encodeURIComponent(storage.name)}` } : {},
|
|
369948
370014
|
async stop() {
|
|
369949
370015
|
const httpServer = server.httpServer;
|
|
369950
370016
|
if (httpServer) {
|
|
@@ -370350,6 +370416,11 @@ function resolveEnvValue(value, resources, secrets, configs, servicePort, servic
|
|
|
370350
370416
|
throw new Error(`Storage "${value.name}" does not have a bucket`);
|
|
370351
370417
|
}
|
|
370352
370418
|
return storage.bucket;
|
|
370419
|
+
case "public_url":
|
|
370420
|
+
if (!storage.publicUrl) {
|
|
370421
|
+
throw new Error(`Storage "${value.name}" is not public`);
|
|
370422
|
+
}
|
|
370423
|
+
return storage.publicUrl;
|
|
370353
370424
|
default:
|
|
370354
370425
|
throw new Error(`Unknown storage attribute: ${String(value.attribute)}`);
|
|
370355
370426
|
}
|
|
@@ -372605,6 +372676,8 @@ async function startResources(options2) {
|
|
|
372605
372676
|
if (instance.endpoint) dbState.endpoint = instance.endpoint;
|
|
372606
372677
|
if (instance.accessKey) dbState.accessKey = instance.accessKey;
|
|
372607
372678
|
if (instance.secretKey) dbState.secretKey = instance.secretKey;
|
|
372679
|
+
if (instance.bucket) dbState.bucket = instance.bucket;
|
|
372680
|
+
if (instance.publicUrl) dbState.publicUrl = instance.publicUrl;
|
|
372608
372681
|
await stateManager.registerDatabase(storage.name, dbState);
|
|
372609
372682
|
}
|
|
372610
372683
|
if (temporalConfigs.length > 0) {
|
|
@@ -374225,7 +374298,8 @@ Add them to the config block in specific.local`
|
|
|
374225
374298
|
type: r.type,
|
|
374226
374299
|
port: r.port,
|
|
374227
374300
|
host: r.host,
|
|
374228
|
-
syncEnabled: r.type === "postgres" && syncDatabases.has(name)
|
|
374301
|
+
syncEnabled: r.type === "postgres" && syncDatabases.has(name),
|
|
374302
|
+
...r.publicUrl ? { publicUrl: r.publicUrl } : {}
|
|
374229
374303
|
})),
|
|
374230
374304
|
hasTemporal,
|
|
374231
374305
|
hasMail,
|
|
@@ -374886,7 +374960,7 @@ function trackEvent(event, properties) {
|
|
|
374886
374960
|
event,
|
|
374887
374961
|
properties: {
|
|
374888
374962
|
...properties,
|
|
374889
|
-
cli_version: "0.1.
|
|
374963
|
+
cli_version: "0.1.188",
|
|
374890
374964
|
platform: process.platform,
|
|
374891
374965
|
node_version: process.version,
|
|
374892
374966
|
project_id: getProjectId()
|
|
@@ -375355,6 +375429,18 @@ import { execFile as execFile8 } from "child_process";
|
|
|
375355
375429
|
|
|
375356
375430
|
// node_modules/.pnpm/@specific+config@file+..+config/node_modules/@specific/config/dist/parser.js
|
|
375357
375431
|
var import_hcl2_json_parser3 = __toESM(require_dist2(), 1);
|
|
375432
|
+
|
|
375433
|
+
// node_modules/.pnpm/@specific+config@file+..+config/node_modules/@specific/config/dist/types.js
|
|
375434
|
+
function extractEnvReferences2(value) {
|
|
375435
|
+
if (typeof value === "string")
|
|
375436
|
+
return [];
|
|
375437
|
+
if (value.type === "interpolated") {
|
|
375438
|
+
return value.parts.filter((p) => p.type === "ref").map((p) => p.ref);
|
|
375439
|
+
}
|
|
375440
|
+
return [value];
|
|
375441
|
+
}
|
|
375442
|
+
|
|
375443
|
+
// node_modules/.pnpm/@specific+config@file+..+config/node_modules/@specific/config/dist/parser.js
|
|
375358
375444
|
var { parseToObject: parseToObject3 } = import_hcl2_json_parser3.default;
|
|
375359
375445
|
function parseReference2(value) {
|
|
375360
375446
|
if (typeof value !== "string") {
|
|
@@ -375446,7 +375532,7 @@ function parseReferenceString2(str) {
|
|
|
375446
375532
|
const storageMatch = str.match(new RegExp(`^storage\\.(${id})\\.(\\w+)$`));
|
|
375447
375533
|
if (storageMatch && storageMatch[1] && storageMatch[2]) {
|
|
375448
375534
|
const attr = storageMatch[2];
|
|
375449
|
-
if (["endpoint", "access_key", "secret_key", "bucket"].includes(attr)) {
|
|
375535
|
+
if (["endpoint", "access_key", "secret_key", "bucket", "public_url"].includes(attr)) {
|
|
375450
375536
|
return {
|
|
375451
375537
|
type: "storage",
|
|
375452
375538
|
name: storageMatch[1],
|
|
@@ -375982,8 +376068,28 @@ function parseStorage2(data) {
|
|
|
375982
376068
|
return [];
|
|
375983
376069
|
}
|
|
375984
376070
|
const result = [];
|
|
375985
|
-
for (const [name] of Object.entries(data)) {
|
|
375986
|
-
|
|
376071
|
+
for (const [name, fieldsArray] of Object.entries(data)) {
|
|
376072
|
+
if (Array.isArray(fieldsArray) && fieldsArray.length > 1) {
|
|
376073
|
+
throw new Error(`Storage "${name}" is defined more than once. Merge the blocks into a single storage "${name}" block.`);
|
|
376074
|
+
}
|
|
376075
|
+
const fields = Array.isArray(fieldsArray) ? fieldsArray[0] : fieldsArray;
|
|
376076
|
+
if (!fields || typeof fields !== "object" || Array.isArray(fields)) {
|
|
376077
|
+
result.push({ name });
|
|
376078
|
+
continue;
|
|
376079
|
+
}
|
|
376080
|
+
const fieldObj = fields;
|
|
376081
|
+
const unknownFields = Object.keys(fieldObj).filter((field) => field !== "public");
|
|
376082
|
+
if (unknownFields.length > 0) {
|
|
376083
|
+
throw new Error(`Storage "${name}" has unsupported field "${unknownFields[0]}". Supported fields: public.`);
|
|
376084
|
+
}
|
|
376085
|
+
const publicValue = Array.isArray(fieldObj.public) ? fieldObj.public[0] : fieldObj.public;
|
|
376086
|
+
if (publicValue !== void 0 && typeof publicValue !== "boolean") {
|
|
376087
|
+
throw new Error(`Storage "${name}" has invalid "public" value. Must be a boolean.`);
|
|
376088
|
+
}
|
|
376089
|
+
result.push({
|
|
376090
|
+
name,
|
|
376091
|
+
...publicValue !== void 0 ? { public: publicValue } : {}
|
|
376092
|
+
});
|
|
375987
376093
|
}
|
|
375988
376094
|
return result;
|
|
375989
376095
|
}
|
|
@@ -376126,6 +376232,146 @@ async function parseConfig2(hcl) {
|
|
|
376126
376232
|
environments: parseEnvironments2(json.environment)
|
|
376127
376233
|
};
|
|
376128
376234
|
}
|
|
376235
|
+
function validateEndpointReferences2(config) {
|
|
376236
|
+
const errors = [];
|
|
376237
|
+
const serviceEndpointsMap = /* @__PURE__ */ new Map();
|
|
376238
|
+
for (const service of config.services) {
|
|
376239
|
+
serviceEndpointsMap.set(service.name, service.endpoints);
|
|
376240
|
+
}
|
|
376241
|
+
const storageByName = new Map(config.storage.map((storage) => [storage.name, storage]));
|
|
376242
|
+
const validateStorageReference = (ownerType, ownerName, key, ref, envLabel2 = "env var") => {
|
|
376243
|
+
const target = storageByName.get(ref.name);
|
|
376244
|
+
if (!target) {
|
|
376245
|
+
errors.push({
|
|
376246
|
+
service: ownerName,
|
|
376247
|
+
message: `${ownerType} "${ownerName}" references unknown storage "${ref.name}" in ${envLabel2} "${key}".`
|
|
376248
|
+
});
|
|
376249
|
+
return;
|
|
376250
|
+
}
|
|
376251
|
+
if (ref.attribute === "public_url" && target.public !== true) {
|
|
376252
|
+
errors.push({
|
|
376253
|
+
service: ownerName,
|
|
376254
|
+
message: `${ownerType} "${ownerName}" references public_url of storage "${ref.name}" in ${envLabel2} "${key}", but that storage is not public. Add \`public = true\` to the storage block.`
|
|
376255
|
+
});
|
|
376256
|
+
}
|
|
376257
|
+
};
|
|
376258
|
+
for (const service of config.services) {
|
|
376259
|
+
if (service.env) {
|
|
376260
|
+
for (const [key, value] of Object.entries(service.env)) {
|
|
376261
|
+
const refs = extractEnvReferences2(value);
|
|
376262
|
+
for (const ref of refs) {
|
|
376263
|
+
if (ref.type === "storage") {
|
|
376264
|
+
validateStorageReference("Service", service.name, key, ref);
|
|
376265
|
+
}
|
|
376266
|
+
if (service.endpoints.length > 1 && ref.type === "port") {
|
|
376267
|
+
errors.push({
|
|
376268
|
+
service: service.name,
|
|
376269
|
+
message: `Service "${service.name}" has multiple endpoints but uses \${port} for env var "${key}". Use \${endpoint.<name>.port} instead.`
|
|
376270
|
+
});
|
|
376271
|
+
}
|
|
376272
|
+
if (ref.type === "service") {
|
|
376273
|
+
const serviceRef = ref;
|
|
376274
|
+
const targetEndpoints = serviceEndpointsMap.get(serviceRef.serviceName);
|
|
376275
|
+
if (!targetEndpoints) {
|
|
376276
|
+
errors.push({
|
|
376277
|
+
service: service.name,
|
|
376278
|
+
message: `Service "${service.name}" references unknown service "${serviceRef.serviceName}" in env var "${key}".`
|
|
376279
|
+
});
|
|
376280
|
+
continue;
|
|
376281
|
+
}
|
|
376282
|
+
if (targetEndpoints.length > 1 && !serviceRef.endpointName) {
|
|
376283
|
+
errors.push({
|
|
376284
|
+
service: service.name,
|
|
376285
|
+
message: `Service "${service.name}" references service "${serviceRef.serviceName}" which has multiple endpoints, but no endpoint name specified in env var "${key}". Use \${service.${serviceRef.serviceName}.endpoint.<name>.${serviceRef.attribute}} instead.`
|
|
376286
|
+
});
|
|
376287
|
+
}
|
|
376288
|
+
if (serviceRef.endpointName) {
|
|
376289
|
+
const endpointExists = targetEndpoints.some((e) => e.name === serviceRef.endpointName);
|
|
376290
|
+
if (!endpointExists) {
|
|
376291
|
+
errors.push({
|
|
376292
|
+
service: service.name,
|
|
376293
|
+
message: `Service "${service.name}" references endpoint "${serviceRef.endpointName}" on service "${serviceRef.serviceName}" in env var "${key}", but that endpoint does not exist.`
|
|
376294
|
+
});
|
|
376295
|
+
}
|
|
376296
|
+
}
|
|
376297
|
+
if (serviceRef.attribute === "public_url") {
|
|
376298
|
+
const endpointName = serviceRef.endpointName;
|
|
376299
|
+
if (endpointName) {
|
|
376300
|
+
const targetEndpoint = targetEndpoints.find((e) => e.name === endpointName);
|
|
376301
|
+
if (targetEndpoint && !targetEndpoint.public) {
|
|
376302
|
+
errors.push({
|
|
376303
|
+
service: service.name,
|
|
376304
|
+
message: `Service "${service.name}" references public_url of endpoint "${endpointName}" on service "${serviceRef.serviceName}" in env var "${key}", but that endpoint is not public. Add \`public = true\` to the endpoint.`
|
|
376305
|
+
});
|
|
376306
|
+
}
|
|
376307
|
+
} else {
|
|
376308
|
+
const defaultEndpoint = targetEndpoints.length === 1 ? targetEndpoints[0] : targetEndpoints.find((e) => e.name === "default");
|
|
376309
|
+
if (defaultEndpoint && !defaultEndpoint.public) {
|
|
376310
|
+
errors.push({
|
|
376311
|
+
service: service.name,
|
|
376312
|
+
message: `Service "${service.name}" references public_url of service "${serviceRef.serviceName}" in env var "${key}", but its endpoint is not public. Add \`public = true\` to the endpoint.`
|
|
376313
|
+
});
|
|
376314
|
+
}
|
|
376315
|
+
}
|
|
376316
|
+
}
|
|
376317
|
+
}
|
|
376318
|
+
if (ref.type === "endpoint") {
|
|
376319
|
+
const endpointRef = ref;
|
|
376320
|
+
const endpointExists = service.endpoints.some((e) => e.name === endpointRef.endpointName);
|
|
376321
|
+
if (!endpointExists) {
|
|
376322
|
+
errors.push({
|
|
376323
|
+
service: service.name,
|
|
376324
|
+
message: `Service "${service.name}" references endpoint "${endpointRef.endpointName}" in env var "${key}", but that endpoint is not defined on this service.`
|
|
376325
|
+
});
|
|
376326
|
+
}
|
|
376327
|
+
}
|
|
376328
|
+
if (ref.type === "volume") {
|
|
376329
|
+
const volumeRef = ref;
|
|
376330
|
+
const volumeExists = service.volumes?.some((v) => v.name === volumeRef.volumeName);
|
|
376331
|
+
if (!volumeExists) {
|
|
376332
|
+
errors.push({
|
|
376333
|
+
service: service.name,
|
|
376334
|
+
message: `Service "${service.name}" references volume "${volumeRef.volumeName}" in env var "${key}", but that volume is not defined on this service.`
|
|
376335
|
+
});
|
|
376336
|
+
}
|
|
376337
|
+
}
|
|
376338
|
+
}
|
|
376339
|
+
}
|
|
376340
|
+
}
|
|
376341
|
+
if (service.dev?.env) {
|
|
376342
|
+
for (const [key, value] of Object.entries(service.dev.env)) {
|
|
376343
|
+
for (const ref of extractEnvReferences2(value)) {
|
|
376344
|
+
if (ref.type === "storage") {
|
|
376345
|
+
validateStorageReference("Service", service.name, key, ref, "dev env var");
|
|
376346
|
+
}
|
|
376347
|
+
}
|
|
376348
|
+
}
|
|
376349
|
+
}
|
|
376350
|
+
}
|
|
376351
|
+
for (const cron of config.crons) {
|
|
376352
|
+
for (const [envLabel2, env2] of [
|
|
376353
|
+
["env var", cron.env],
|
|
376354
|
+
["dev env var", cron.dev?.env]
|
|
376355
|
+
]) {
|
|
376356
|
+
if (!env2)
|
|
376357
|
+
continue;
|
|
376358
|
+
for (const [key, value] of Object.entries(env2)) {
|
|
376359
|
+
for (const ref of extractEnvReferences2(value)) {
|
|
376360
|
+
if (ref.type === "storage") {
|
|
376361
|
+
validateStorageReference("Cron", cron.name, key, ref, envLabel2);
|
|
376362
|
+
}
|
|
376363
|
+
}
|
|
376364
|
+
}
|
|
376365
|
+
}
|
|
376366
|
+
}
|
|
376367
|
+
return errors;
|
|
376368
|
+
}
|
|
376369
|
+
function assertValidReferences(config) {
|
|
376370
|
+
const errors = validateEndpointReferences2(config);
|
|
376371
|
+
if (errors.length > 0) {
|
|
376372
|
+
throw new Error(errors.map((error) => error.message).join("\n"));
|
|
376373
|
+
}
|
|
376374
|
+
}
|
|
376129
376375
|
|
|
376130
376376
|
// src/commands/check.tsx
|
|
376131
376377
|
async function runReshapeCheck(migrationsDir) {
|
|
@@ -376161,6 +376407,7 @@ function CheckUI() {
|
|
|
376161
376407
|
const hcl = fs22.readFileSync(configPath, "utf-8");
|
|
376162
376408
|
try {
|
|
376163
376409
|
const config2 = await parseConfig2(hcl);
|
|
376410
|
+
assertValidReferences(config2);
|
|
376164
376411
|
const localPath = path20.join(process.cwd(), LOCAL_FILE);
|
|
376165
376412
|
if (fs22.existsSync(localPath)) {
|
|
376166
376413
|
const localContent = fs22.readFileSync(localPath, "utf-8");
|
|
@@ -376236,7 +376483,7 @@ function CheckUI() {
|
|
|
376236
376483
|
const reshapeChecks = state.reshapeChecks ?? [];
|
|
376237
376484
|
const reshapeErrors = reshapeChecks.filter((r) => !r.success);
|
|
376238
376485
|
const hasReshapeErrors = reshapeErrors.length > 0;
|
|
376239
|
-
return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: hasReshapeErrors ? "yellow" : "green" }, hasReshapeErrors ? "Configuration is valid but reshape migrations have errors" : "Configuration is valid"), config.builds.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Builds (", config.builds.length, "):"), config.builds.map((b) => /* @__PURE__ */ React3.createElement(Text3, { key: b.name }, " ", "- ", b.name, b.base ? ` (${b.base})` : "", b.dockerfile ? ` (dockerfile: ${b.dockerfile})` : ""))), config.services.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Services (", config.services.length, "):"), config.services.map((s) => /* @__PURE__ */ React3.createElement(Text3, { key: s.name }, " ", "- ", s.name, s.build ? ` (build: ${s.build.name})` : "", s.image ? ` (image: ${s.image})` : ""))), config.crons.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Crons (", config.crons.length, "):"), config.crons.map((c) => /* @__PURE__ */ React3.createElement(Text3, { key: c.name }, " ", "- ", c.name, " (", c.schedule, ")", c.build ? ` (build: ${c.build.name})` : "", c.image ? ` (image: ${c.image})` : ""))), config.postgres.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Postgres (", config.postgres.length, "):"), config.postgres.map((p) => /* @__PURE__ */ React3.createElement(Text3, { key: p.name }, " ", "- ", p.name, p.reshape?.enabled ? " (reshape enabled)" : ""))), reshapeChecks.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Reshape Migrations:"), reshapeChecks.map((r) => /* @__PURE__ */ React3.createElement(Box3, { key: r.databaseName, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, null, " ", /* @__PURE__ */ React3.createElement(Text3, { color: r.success ? "green" : "red" }, r.success ? "pass" : "fail"), " ", r.databaseName, " (", r.migrationsDir, ")"), r.error && /* @__PURE__ */ React3.createElement(Text3, { color: "red" }, " ", r.error)))), config.redis.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Redis (", config.redis.length, "):"), config.redis.map((r) => /* @__PURE__ */ React3.createElement(Text3, { key: r.name }, " ", "- ", r.name))), config.storage.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Storage (", config.storage.length, "):"), config.storage.map((s) => /* @__PURE__ */ React3.createElement(Text3, { key: s.name }, " ", "- ", s.name))), config.configs.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Config (", config.configs.length, "):"), config.configs.map((c) => /* @__PURE__ */ React3.createElement(Text3, { key: c.name }, " ", "- ", c.name, c.default ? ` (default: ${c.default})` : ""))), config.environments.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Environments (", config.environments.length, "):"), config.environments.map((e) => /* @__PURE__ */ React3.createElement(Text3, { key: e.name }, " ", "- ", e.name))));
|
|
376486
|
+
return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: hasReshapeErrors ? "yellow" : "green" }, hasReshapeErrors ? "Configuration is valid but reshape migrations have errors" : "Configuration is valid"), config.builds.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Builds (", config.builds.length, "):"), config.builds.map((b) => /* @__PURE__ */ React3.createElement(Text3, { key: b.name }, " ", "- ", b.name, b.base ? ` (${b.base})` : "", b.dockerfile ? ` (dockerfile: ${b.dockerfile})` : ""))), config.services.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Services (", config.services.length, "):"), config.services.map((s) => /* @__PURE__ */ React3.createElement(Text3, { key: s.name }, " ", "- ", s.name, s.build ? ` (build: ${s.build.name})` : "", s.image ? ` (image: ${s.image})` : ""))), config.crons.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Crons (", config.crons.length, "):"), config.crons.map((c) => /* @__PURE__ */ React3.createElement(Text3, { key: c.name }, " ", "- ", c.name, " (", c.schedule, ")", c.build ? ` (build: ${c.build.name})` : "", c.image ? ` (image: ${c.image})` : ""))), config.postgres.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Postgres (", config.postgres.length, "):"), config.postgres.map((p) => /* @__PURE__ */ React3.createElement(Text3, { key: p.name }, " ", "- ", p.name, p.reshape?.enabled ? " (reshape enabled)" : ""))), reshapeChecks.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Reshape Migrations:"), reshapeChecks.map((r) => /* @__PURE__ */ React3.createElement(Box3, { key: r.databaseName, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, null, " ", /* @__PURE__ */ React3.createElement(Text3, { color: r.success ? "green" : "red" }, r.success ? "pass" : "fail"), " ", r.databaseName, " (", r.migrationsDir, ")"), r.error && /* @__PURE__ */ React3.createElement(Text3, { color: "red" }, " ", r.error)))), config.redis.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Redis (", config.redis.length, "):"), config.redis.map((r) => /* @__PURE__ */ React3.createElement(Text3, { key: r.name }, " ", "- ", r.name))), config.storage.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Storage (", config.storage.length, "):"), config.storage.map((s) => /* @__PURE__ */ React3.createElement(Text3, { key: s.name }, " ", "- ", s.name, s.public ? " (public)" : ""))), config.configs.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Config (", config.configs.length, "):"), config.configs.map((c) => /* @__PURE__ */ React3.createElement(Text3, { key: c.name }, " ", "- ", c.name, c.default ? ` (default: ${c.default})` : ""))), config.environments.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Environments (", config.environments.length, "):"), config.environments.map((e) => /* @__PURE__ */ React3.createElement(Text3, { key: e.name }, " ", "- ", e.name))));
|
|
376240
376487
|
}
|
|
376241
376488
|
function checkCommand() {
|
|
376242
376489
|
render3(/* @__PURE__ */ React3.createElement(CheckUI, null));
|
|
@@ -378528,7 +378775,13 @@ async function execCommand(serviceName, command, instanceKey = "default", option
|
|
|
378528
378775
|
endpoint: dbState.endpoint,
|
|
378529
378776
|
accessKey: dbState.accessKey,
|
|
378530
378777
|
secretKey: dbState.secretKey,
|
|
378531
|
-
|
|
378778
|
+
// State written by older SDK versions predates the bucket field;
|
|
378779
|
+
// object-store buckets were always named after the resource.
|
|
378780
|
+
bucket: dbState.bucket ?? (dbState.engine === "object_store" ? name : void 0),
|
|
378781
|
+
// Same legacy-state fallback for publicUrl: an older dev daemon
|
|
378782
|
+
// never records it, but the local URL is derivable from the
|
|
378783
|
+
// endpoint when the config marks the storage public.
|
|
378784
|
+
publicUrl: dbState.publicUrl ?? (dbState.engine === "object_store" && dbState.endpoint && config.storage.find((s) => s.name === name)?.public === true ? `${dbState.endpoint}/${encodeURIComponent(name)}` : void 0),
|
|
378532
378785
|
syncUrl: dbState.syncUrl,
|
|
378533
378786
|
syncSecret: dbState.syncSecret,
|
|
378534
378787
|
reshapeEnabled: dbState.reshapeEnabled,
|
|
@@ -379301,7 +379554,7 @@ function compareVersions(a, b) {
|
|
|
379301
379554
|
return 0;
|
|
379302
379555
|
}
|
|
379303
379556
|
async function checkForUpdate() {
|
|
379304
|
-
const currentVersion = "0.1.
|
|
379557
|
+
const currentVersion = "0.1.188";
|
|
379305
379558
|
const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
|
|
379306
379559
|
if (!response.ok) {
|
|
379307
379560
|
throw new Error(`Failed to check for updates: HTTP ${response.status}`);
|
|
@@ -380218,7 +380471,7 @@ async function domainAddCommand(service, domain, options2) {
|
|
|
380218
380471
|
var program = new Command();
|
|
380219
380472
|
var env = "production";
|
|
380220
380473
|
var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
|
|
380221
|
-
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.
|
|
380474
|
+
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.188").enablePositionalOptions();
|
|
380222
380475
|
program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").addHelpText("after", `
|
|
380223
380476
|
Examples:
|
|
380224
380477
|
$ specific init
|
package/dist/docs/storage.md
CHANGED
|
@@ -36,6 +36,36 @@ storage "uploads" {}
|
|
|
36
36
|
- `access_key` - Access key for authentication
|
|
37
37
|
- `secret_key` - Secret key for authentication
|
|
38
38
|
- `bucket` - Bucket name
|
|
39
|
+
- `public_url` - HTTPS origin for anonymous reads (public buckets only)
|
|
40
|
+
|
|
41
|
+
## Public buckets
|
|
42
|
+
|
|
43
|
+
Public access is opt-in:
|
|
44
|
+
|
|
45
|
+
```hcl
|
|
46
|
+
storage "assets" {
|
|
47
|
+
public = true
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
service "web" {
|
|
51
|
+
build = build.web
|
|
52
|
+
|
|
53
|
+
env = {
|
|
54
|
+
ASSETS_PUBLIC_URL = storage.assets.public_url
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`public_url` has no trailing slash. Append an object key to it, for example
|
|
60
|
+
`${ASSETS_PUBLIC_URL}/images/logo.png`.
|
|
61
|
+
|
|
62
|
+
Anonymous users can read objects, but uploads and deletes still require
|
|
63
|
+
credentials. Directory listing is disabled. CORS, cache headers, and custom
|
|
64
|
+
domains must be configured separately. Removing `public = true` makes the
|
|
65
|
+
bucket private again on the next deployment.
|
|
66
|
+
|
|
67
|
+
Preview buckets contain a fork of their parent bucket. Making one public can
|
|
68
|
+
expose copied parent data.
|
|
39
69
|
|
|
40
70
|
## Production management
|
|
41
71
|
|
package/package.json
CHANGED
/package/dist/admin/_next/static/{6oKX20WXYrqT7PTdniaYQ → 3XymYpBfQIIiwwuOTHt6x}/_buildManifest.js
RENAMED
|
File without changes
|
|
File without changes
|
/package/dist/admin/_next/static/{6oKX20WXYrqT7PTdniaYQ → 3XymYpBfQIIiwwuOTHt6x}/_ssgManifest.js
RENAMED
|
File without changes
|