@port60/template-kit 0.14.1 → 0.14.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@port60/template-kit",
3
- "version": "0.14.1",
3
+ "version": "0.14.3",
4
4
  "description": "Build Port60 site templates locally: scaffold, live-preview, validate against the platform contract, and package for studio upload. AI-agent ready \u2014 every scaffold ships AGENTS.md and validate emits machine-readable JSON.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
2
2
  import { resolve, join } from 'node:path';
3
3
  import { validateArtifact } from '../vendor/validator/validate.mjs';
4
- import { loadArtifactDir } from '../lib/artifactFiles.mjs';
4
+ import { loadArtifactDir, skippedNotice } from '../lib/artifactFiles.mjs';
5
5
  import { buildZip } from '../lib/zip.mjs';
6
6
 
7
7
  /**
@@ -9,7 +9,8 @@ import { buildZip } from '../lib/zip.mjs';
9
9
  * saves the round trip), then zip EXACTLY the contract-shaped file set into
10
10
  * `dist/<name>-<version>.zip`, the artifact the studio's upload lane accepts as-is. `dist/` is
11
11
  * the build output and nothing else: recreated on every run, so a stale archive from a previous
12
- * version can never sit beside the fresh one, and gitignored by the scaffold.
12
+ * version can never sit beside the fresh one, and gitignored by the scaffold. Anything the rules
13
+ * left out (an image in assets/, a stray script) is listed under the result, never silently.
13
14
  */
14
15
  export async function packageCmd(args) {
15
16
  const dir = resolve(args._[0] ?? '.');
@@ -26,5 +27,6 @@ export async function packageCmd(args) {
26
27
  const out = join(outDir, `${manifest.name}-${manifest.version}.zip`);
27
28
  writeFileSync(out, buildZip(Object.entries(files).map(([path, content]) => ({ path, content }))));
28
29
  console.log(`✓ ${out}`);
30
+ for (const line of skippedNotice(dir)) console.log(line);
29
31
  console.log(' Upload it from your studio (tenant admin → Studio → your template → Versions).');
30
32
  }
@@ -1,6 +1,6 @@
1
1
  import { resolve } from 'node:path';
2
2
  import { validateArtifact } from '../vendor/validator/validate.mjs';
3
- import { loadArtifactDir } from '../lib/artifactFiles.mjs';
3
+ import { loadArtifactDir, skippedNotice } from '../lib/artifactFiles.mjs';
4
4
  import { buildZip } from '../lib/zip.mjs';
5
5
  import { accessToken, api, apiBase, loadCredentials, requireCredentials } from '../lib/auth.mjs';
6
6
 
@@ -44,6 +44,7 @@ export async function publish(args) {
44
44
  }
45
45
  const zip = buildZip(Object.entries(files).map(([path, content]) => ({ path, content })));
46
46
  console.log(`✓ ${manifest.name} ${manifest.version} validated, uploading${ci ? ' from CI' : ''}…`);
47
+ for (const line of skippedNotice(dir)) console.log(line);
47
48
 
48
49
  if (ci) {
49
50
  await publishFromCi(args, zip);
@@ -125,8 +126,7 @@ async function publishFromCi(args, zip) {
125
126
  if (!res.ok) {
126
127
  console.error(`✗ Publish refused (${res.status}): ${await errorMessage(res)}`);
127
128
  if (res.status === 403) {
128
- console.error(' Trust this repository under Automate publishing in your studio,');
129
- console.error(' and check the workflow runs from a ref the trust allows (refs/tags/v* by default).');
129
+ console.error(' Trust this repository under Automate in your studio (one field: owner/name).');
130
130
  }
131
131
  process.exit(1);
132
132
  }
@@ -34,8 +34,11 @@ platform accepts it; if it fails here, the upload will fail identically.
34
34
  \`supports.sections\`. Charities compose pages from a **closed catalogue** of section types
35
35
  (see \`npm run validate\` output or the docs), you cannot invent new types.
36
36
  - \`pages/<page>.liquid\`, optional full-page templates for \`supports.pageTemplates\`.
37
- - \`assets/theme.css\`, required, your entire look. More \`.css\` files under \`assets/\` are
38
- allowed. **No images, no JS**, they are refused at upload.
37
+ - \`assets/theme.css\`, required, your entire look, and the ONLY stylesheet the platform loads
38
+ (other \`.css\` files under \`assets/\` are packaged but never loaded, so keep everything in
39
+ it). **No images, no fonts, no JS**: \`package\` and \`publish\` leave them out and list what
40
+ they left out; the upload refuses them. Photographs belong in the charity's media library;
41
+ decorative textures go inline in the CSS as data URIs.
39
42
 
40
43
  ## The rules the validator enforces (do not fight them)
41
44
 
@@ -1,4 +1,4 @@
1
- // The contract-shaped file set of an artifact directory — the same selection the platform's
1
+ // The contract-shaped file set of an artifact directory, the same selection the platform's
2
2
  // upload intake allow-lists: manifest, layout, section/page renderers, css assets. Anything else
3
3
  // in the directory is not part of an artifact and is neither validated nor packaged.
4
4
  import { readFileSync, readdirSync, existsSync } from 'node:fs';
@@ -26,3 +26,43 @@ export function loadArtifactDir(root) {
26
26
  }
27
27
  return files;
28
28
  }
29
+
30
+ // What the artifact rules leave behind. A photograph, a font or a script dropped next to the
31
+ // renderers is the classic surprise: it may even show in a local page, then the zip and the upload
32
+ // carry nothing of it. `package` and `publish` print this list so the author learns it from the
33
+ // kit, not from a broken URL on a live site. Project files that legitimately live beside a
34
+ // template (README, package.json, node_modules, dist, preview content, design notes) are not
35
+ // reported; only the three artifact folders and top-level binaries are.
36
+ const ACCEPTED = { sections: /\.liquid$/, pages: /\.liquid$/, assets: /\.css$/ };
37
+ const TOP_LEVEL_BINARY = /\.(png|jpe?g|gif|webp|avif|svg|ico|woff2?|ttf|otf|eot|mp4|webm|mp3|m4a|js|mjs|cjs|ts)$/i;
38
+
39
+ export function listSkippedFiles(root) {
40
+ const skipped = [];
41
+ const walk = (dir, rel, accept, depth) => {
42
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
43
+ if (entry.name.startsWith('.')) continue;
44
+ const path = `${rel}/${entry.name}`;
45
+ if (entry.isDirectory()) walk(join(dir, entry.name), path, accept, depth + 1);
46
+ // Only the folder's own files are read; anything nested is left out whatever its name.
47
+ else if (depth > 0 || !accept.test(entry.name)) skipped.push(path);
48
+ }
49
+ };
50
+ for (const [dir, accept] of Object.entries(ACCEPTED)) {
51
+ if (existsSync(join(root, dir))) walk(join(root, dir), dir, accept, 0);
52
+ }
53
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
54
+ if (entry.isFile() && TOP_LEVEL_BINARY.test(entry.name)) skipped.push(entry.name);
55
+ }
56
+ return skipped.sort();
57
+ }
58
+
59
+ /** The lines `package`/`publish` print under the result when something was left out; empty otherwise. */
60
+ export function skippedNotice(root) {
61
+ const skipped = listSkippedFiles(root);
62
+ if (skipped.length === 0) return [];
63
+ return [
64
+ ` left out (not part of a template): ${skipped.join(', ')}`,
65
+ ' Templates ship Liquid and CSS only; photographs belong in the charity\'s media library.',
66
+ ' See https://developers.port60.com/guides/publishing/#what-the-zip-contains'
67
+ ];
68
+ }