auto: PR for feat/image-optimization #67
@@ -97,13 +97,15 @@ Simple index to quickly find assets by group:
|
|||||||
|
|
||||||
### assets.integrity.index.json
|
### assets.integrity.index.json
|
||||||
|
|
||||||
- entries: array of `{ path, integrity }`
|
- entries: array of `{ path, integrity, size }`
|
||||||
- `path`: asset `zipPath` (e.g. `assets/abcd1234.png` or grouped `assets/images/...`)
|
- `path`: asset `zipPath` (e.g. `assets/abcd1234.png` or grouped `assets/images/...`)
|
||||||
- `integrity`: `sha256-<base64>` (real SHA-256 of the asset bytes)
|
- `integrity`: `sha256-<base64>` (real SHA-256 of the asset bytes)
|
||||||
|
- `size`: byte length of the asset
|
||||||
- bundleHash: `sha256-<base64>` over a deterministic join of `path` and `integrity` for all entries
|
- bundleHash: `sha256-<base64>` over a deterministic join of `path` and `integrity` for all entries
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
- Verify file-level integrity by recomputing SHA-256(base64) of each asset and comparing with `entries[].integrity`.
|
- Verify file-level integrity by recomputing SHA-256(base64) of each asset and comparing with `entries[].integrity`.
|
||||||
|
- Verify file size matches `entries[].size`.
|
||||||
- Verify bundle integrity by recomputing `bundleHash` from sorted entries; useful for quick tamper detection.
|
- Verify bundle integrity by recomputing `bundleHash` from sorted entries; useful for quick tamper detection.
|
||||||
|
|
||||||
### Google Sheets template (optional)
|
### Google Sheets template (optional)
|
||||||
|
|||||||
+14
-2
@@ -68,13 +68,25 @@ export function buildExtrasForPage(page: Page, overrides?: ExtrasOverrides): Rec
|
|||||||
const code = `/**
|
const code = `/**
|
||||||
* Google Apps Script Web App for form submissions
|
* Google Apps Script Web App for form submissions
|
||||||
* - Deploy as Web App and set URL as form action if needed.
|
* - Deploy as Web App and set URL as form action if needed.
|
||||||
|
* @tool: landing-builder
|
||||||
|
* @version: 1.0.0
|
||||||
*/
|
*/
|
||||||
function doPost(e){
|
function doPost(e){
|
||||||
// TODO: handle submission to Google Sheets
|
try {
|
||||||
|
// TODO: map incoming fields to sheet columns
|
||||||
|
// Example:
|
||||||
|
// const payload = JSON.parse(e.postData.contents || '{}');
|
||||||
|
// const row = [payload.name, payload.email, new Date()];
|
||||||
|
// SpreadsheetApp.openById('YOUR_SHEET_ID').getSheetByName('Sheet1').appendRow(row);
|
||||||
return ContentService.createTextOutput('OK')
|
return ContentService.createTextOutput('OK')
|
||||||
|
} catch (e) {
|
||||||
|
return ContentService
|
||||||
|
.createTextOutput('ERROR: ' + (e && (e as any).message ? (e as any).message : 'unknown'))
|
||||||
|
.setMimeType(ContentService.MimeType.TEXT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
const readme = `Google Sheets Apps Script Template\n\n1) Apps Script에서 새 프로젝트를 만들고 아래 Code.gs를 붙여넣으세요.\n2) 배포 > 웹 앱으로 배포 후 URL을 복사해 사용하세요.`
|
const readme = `Google Sheets Apps Script Template\n\n1) Apps Script에서 새 프로젝트를 만들고 아래 Code.gs를 붙여넣으세요.\n2) Deploy as Web App로 배포 후 URL을 복사해 사용하세요.\n3) Map fields to columns: 제출 payload의 필드를 시트 컬럼에 매핑하세요.`
|
||||||
out['sheets/Code.gs'] = toBytes(code)
|
out['sheets/Code.gs'] = toBytes(code)
|
||||||
out['sheets/README.txt'] = toBytes(readme)
|
out['sheets/README.txt'] = toBytes(readme)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { buildExtrasForPage } from '@/lib/exporter/extras'
|
||||||
|
import type { Page } from '@/lib/schema/page'
|
||||||
|
|
||||||
|
function td(b: Uint8Array){ return new TextDecoder().decode(b) }
|
||||||
|
|
||||||
|
const basePage: Page = {
|
||||||
|
title: 'Test',
|
||||||
|
seo: { title: 'T', description: 'D' },
|
||||||
|
theme: { primaryColor: '#0ea5e9', fontFamily: 'Inter' },
|
||||||
|
analytics: { ga4MeasurementId: '', metaPixelId: '', customHeadHtml: '' },
|
||||||
|
sections: [],
|
||||||
|
form: {
|
||||||
|
fields: [],
|
||||||
|
actionUrl: '',
|
||||||
|
spamProtection: { honeypotFieldName: '_hp', minSubmitSeconds: 2 },
|
||||||
|
},
|
||||||
|
locale: 'en',
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Sheets template enhance', () => {
|
||||||
|
it('includes version/meta and mapping/error guide in Code.gs and README', () => {
|
||||||
|
const extras = buildExtrasForPage(basePage, { sheetsMode: 'include' })
|
||||||
|
const code = td(extras['sheets/Code.gs'])
|
||||||
|
const readme = td(extras['sheets/README.txt'])
|
||||||
|
|
||||||
|
// version/meta header
|
||||||
|
expect(code).toMatch(/@version:\s*\d+\.\d+\.\d+/)
|
||||||
|
expect(code).toMatch(/@tool:\s*landing-builder/i)
|
||||||
|
|
||||||
|
// field mapping and error handling guides
|
||||||
|
expect(code).toMatch(/TODO:\s*map incoming fields to sheet columns/i)
|
||||||
|
expect(code).toMatch(/try\s*\{/)
|
||||||
|
expect(code).toMatch(/catch\s*\(e\)/)
|
||||||
|
|
||||||
|
// README includes setup and mapping hint
|
||||||
|
expect(readme).toMatch(/Apps Script/i)
|
||||||
|
expect(readme).toMatch(/Deploy as Web App/i)
|
||||||
|
expect(readme).toMatch(/Map fields to columns/i)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('respects exclude mode', () => {
|
||||||
|
const extras = buildExtrasForPage(basePage, { sheetsMode: 'exclude' })
|
||||||
|
expect(Object.keys(extras).some(k => k.startsWith('sheets/'))).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user