@minamocms/framework 0.10.1 → 0.10.2

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/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ All notable changes to this package are documented here. Format loosely follows
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.10.2] - 2026-11-04
8
+
9
+ ### Fixed
10
+
11
+ - **A site with no `media/` folder serves and stores media normally.** `media/` is git-ignored, so a site deployed by a host that builds from its repository (Railway, for one) starts without it. Until now every request for a media file there answered 500 instead of 404, and the first upload failed the same way. A missing folder now reads as an empty one, and the first upload creates it.
12
+
7
13
  ## [0.10.1] - 2026-10-15
8
14
 
9
15
  ### Changed
package/README.md CHANGED
@@ -54,7 +54,7 @@ npm install
54
54
  npm start
55
55
  ```
56
56
 
57
- Visit `http://localhost:3000`. `create-site` prints a real API token to
57
+ Visit `http://localhost:3000`. `create-site` prints a real access token to
58
58
  your terminal when it scaffolds the site - that's what the Minamo admin
59
59
  (or a plain `curl`) uses to authenticate against the site's
60
60
  `/v1/` API. Lost it, or need a second one? See the `mint-token` command in
@@ -11,7 +11,7 @@ try {
11
11
  const { raw } = scaffoldSite(targetDir);
12
12
  console.log(`Created a new site at ${targetDir}`);
13
13
  console.log('');
14
- console.log('Your API token (save this now - it will not be shown again):');
14
+ console.log('Your access token (save this now - it will not be shown again):');
15
15
  console.log(` ${raw}`);
16
16
  console.log('');
17
17
  console.log('Next steps:');
@@ -12,7 +12,7 @@ try {
12
12
  const scopes = parseScopes(scopesArg);
13
13
  const siteDir = resolve(process.cwd(), targetArg);
14
14
  const { raw } = mintToken(siteDir, scopes);
15
- console.log('New API token (save this now - it will not be shown again):');
15
+ console.log('New access token (save this now - it will not be shown again):');
16
16
  console.log(` ${raw}`);
17
17
  console.log('');
18
18
  console.log(`Scopes: ${scopes.join(', ')}`);
@@ -44,6 +44,6 @@ export function mintToken(siteDir, scopes) {
44
44
  tokens.push({ hash: token.hash, scopes });
45
45
  config.tokens = tokens;
46
46
  writeFileSync(configPath, JSON.stringify(config, null, 2));
47
- commitPaths(siteDir, ['vhost/site.config.json'], 'chore: mint a new API token', CHECKPOINT_AUTHOR);
47
+ commitPaths(siteDir, ['vhost/site.config.json'], 'chore: mint a new access token', CHECKPOINT_AUTHOR);
48
48
  return { raw: token.raw };
49
49
  }
@@ -452,7 +452,7 @@ npm run dev # same, plus auto-restart whenever a theme/ file changes - u
452
452
  npm run stop # stops a site started by any of the above, from any terminal
453
453
  ```
454
454
 
455
- A site is deployed once; after that, `npm run pull` and `npm run push` move changes between this copy and the live site. `npm run pull` makes this copy match the live site for whatever is chosen - content (pages, menus, redirects, site settings, images), theme, or both - asking for the address and API token (or `npm run pull -- <its URL>` with `CMS_TOKEN` set). It refuses while the parts it would overwrite have uncommitted changes, and commits nothing. `npm run push` sends back only what changed here since the last pull: anything an editor changed on the live site since then stops the push and is listed instead of overwritten, it shows everything it will change, and it asks for the site's address to be typed first. After `npm run upgrade` it can also upgrade the live site's CMS. Never run push on a user's behalf without them confirming.
455
+ A site is deployed once; after that, `npm run pull` and `npm run push` move changes between this copy and the live site. `npm run pull` makes this copy match the live site for whatever is chosen - content (pages, menus, redirects, site settings, images), theme, or both - asking for the address and access token (or `npm run pull -- <its URL>` with `CMS_TOKEN` set). It refuses while the parts it would overwrite have uncommitted changes, and commits nothing. `npm run push` sends back only what changed here since the last pull: anything an editor changed on the live site since then stops the push and is listed instead of overwritten, it shows everything it will change, and it asks for the site's address to be typed first. After `npm run upgrade` it can also upgrade the live site's CMS. Never run push on a user's behalf without them confirming.
456
456
 
457
457
  `npm run dev` only watches `theme/` - content changes (via the API) already show up on the next request with no restart needed, so there's nothing to gain watching `content/` too.
458
458
 
@@ -31,6 +31,11 @@ export function openLocalFsMediaDriver(root) {
31
31
  });
32
32
  },
33
33
  async put(filename, bytes) {
34
+ // The root first: sanitisePath resolves the root's real path, which
35
+ // fails outright on a fresh deploy whose media folder was never
36
+ // created (media/ is git-ignored, so a host building from the
37
+ // repository has none).
38
+ mkdirSync(root, { recursive: true });
34
39
  const resolved = sanitisePath(root, filename);
35
40
  // Harmless no-op for a flat, content-addressed namespace (media
36
41
  // filenames never contain subdirectories) - matches
@@ -45,6 +50,11 @@ export function openLocalFsMediaDriver(root) {
45
50
  writeFileSync(resolved, bytes);
46
51
  },
47
52
  async get(filename) {
53
+ // No media folder yet is no media yet: nothing to find, and a read
54
+ // has no business creating the folder.
55
+ if (!existsSync(root)) {
56
+ return null;
57
+ }
48
58
  let resolved;
49
59
  try {
50
60
  resolved = sanitisePath(root, filename);
@@ -70,6 +80,9 @@ export function openLocalFsMediaDriver(root) {
70
80
  // reading as "already didn't exist", matching
71
81
  // manage-redirects.ts's own not-found-is-a-distinct-reason
72
82
  // precedent.
83
+ if (!existsSync(root)) {
84
+ return false;
85
+ }
73
86
  const resolved = sanitisePath(root, filename);
74
87
  if (!existsSync(resolved)) {
75
88
  return false;
package/dist/server.js CHANGED
@@ -219,7 +219,7 @@ export async function startServer(siteRoot, options = {}) {
219
219
  try {
220
220
  tunnel = await (options.startTunnel ?? startDevTunnel)(serverConfig.port);
221
221
  console.log(`Tunnel open: ${tunnel.url}`);
222
- console.log('This URL is publicly reachable for as long as this process runs - anyone with it (and a valid API token) can reach this site. Do not leave it running unattended.');
222
+ console.log('This URL is publicly reachable for as long as this process runs - anyone with it (and a valid access token) can reach this site. Do not leave it running unattended.');
223
223
  }
224
224
  catch (error) {
225
225
  // The site itself is already up and correctly serving locally -
@@ -5,7 +5,7 @@ export interface SyncArgs {
5
5
  }
6
6
  export declare function parseSyncArgs(argv: string[]): SyncArgs;
7
7
  export declare function ask(question: string, hidden?: boolean): Promise<string>;
8
- export declare const TOKEN_HELP = "The token is the live site's API token (the same one the admin uses), with the \"content\" and \"media\" scopes.";
8
+ export declare const TOKEN_HELP = "The token is the live site's access token (the same one the admin uses), with the \"content\" and \"media\" scopes.";
9
9
  export declare function resolveToken(args: SyncArgs, siteUrl: string): Promise<string>;
10
10
  export interface PartOption {
11
11
  available: boolean;
@@ -23,11 +23,11 @@ export async function ask(question, hidden = false) {
23
23
  throw error;
24
24
  }
25
25
  }
26
- export const TOKEN_HELP = 'The token is the live site\'s API token (the same one the admin uses), with the "content" and "media" scopes.';
26
+ export const TOKEN_HELP = 'The token is the live site\'s access token (the same one the admin uses), with the "content" and "media" scopes.';
27
27
  // CMS_TOKEN and --token still work for scripts, but either leaves the
28
28
  // token in shell history; asked for, it goes nowhere.
29
29
  export async function resolveToken(args, siteUrl) {
30
- const token = args.token ?? process.env.CMS_TOKEN ?? (await ask(`API token for ${siteUrl} (hidden): `, true));
30
+ const token = args.token ?? process.env.CMS_TOKEN ?? (await ask(`Access token for ${siteUrl} (hidden): `, true));
31
31
  if (!token) {
32
32
  console.error('No token entered.');
33
33
  console.error(TOKEN_HELP);
@@ -1,5 +1,5 @@
1
1
  // Asks questions on the terminal - the live site's address, then its
2
- // API token without showing it. A token typed on the command line (or
2
+ // access token without showing it. A token typed on the command line (or
3
3
  // in CMS_TOKEN=... before it) ends up in shell history; typed here, it
4
4
  // goes nowhere but this process.
5
5
  export class PromptCancelledError extends Error {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minamocms/framework",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"