@ampless/plugin-gtm 0.1.1-alpha.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ampless contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.ja.md ADDED
@@ -0,0 +1,84 @@
1
+ > English: [README.md](./README.md)
2
+ >
3
+
4
+ # @ampless/plugin-gtm
5
+
6
+ [ampless](https://github.com/heavymoons/ampless) 向け Google Tag Manager プラグイン。
7
+
8
+ > **プレリリース / アルファ版。** v1.0 まではマイナーバージョンでも破壊的変更が入る可能性があります。
9
+
10
+ descriptor ベースのプラグイン head/body 注入 API を使って、公開ページに GTM 標準の 2 つのスニペットを挿入します。
11
+
12
+ 1. `<head>` 内に async ローダーのインライン `<script>`
13
+ 2. `<body>` 末尾に対応する `<noscript>` iframe(JavaScript 無効の訪問者でも GTM 経由でページビューを登録できる)
14
+
15
+ コンテナ ID はデプロイ後 **`/admin/plugins` から編集可能** で、`cms.config.ts` の constructor 引数は初期デフォルトのシードに過ぎません。AWS のデータ権限は不要で、`trust_level` は `untrusted`、公開 Next.js プロセス内で描画時に動きます。
16
+
17
+ ## インストール
18
+
19
+ ```bash
20
+ npm install @ampless/plugin-gtm@alpha
21
+ ```
22
+
23
+ ## 設定
24
+
25
+ `cms.config.ts` に記述します:
26
+
27
+ ```ts
28
+ import { defineConfig } from 'ampless'
29
+ import gtmPlugin from '@ampless/plugin-gtm'
30
+
31
+ export default defineConfig({
32
+ // ...
33
+ plugins: [
34
+ gtmPlugin({
35
+ // 初期コンテナ ID。デプロイ後は /admin/plugins から編集可能。
36
+ // 空にしておけば無効化したまま ship できる。
37
+ containerId: '',
38
+ }),
39
+ ],
40
+ })
41
+ ```
42
+
43
+ | オプション | デフォルト | 備考 |
44
+ |---|---|---|
45
+ | `containerId` | `''` | 初期 GTM コンテナ ID。例: `GTM-XXXXXXX`。マニフェストの default を seed するだけで、ライブ値はリクエスト時に `/admin/plugins` から読み出される。空文字でプラグインを無効化したまま ship できる。 |
46
+ | `instanceId` | `'gtm'` | script / noscript 要素 id と設定保存キーの namespace。同サイトに複数の GTM コンテナを入れる場合に分ける。 |
47
+
48
+ ## 管理画面からコンテナ ID を編集する
49
+
50
+ デプロイ後、コンテナ ID は `/admin/plugins` → **Google Tag Manager** で編集できます。空文字保存で `cms.config.ts` から削除せずに無効化、`GTM-XXXXXXX` を保存すれば有効化されます。変更内容は site-settings の S3 ミラーが反映された後(数秒)に公開サイトに反映されます。admin form は cache invalidation を少し遅延発火するので、processor の rebuild 完了前に公開側が古い snapshot を fetch する race を避けています。
51
+
52
+ ## コンテナ ID の取得
53
+
54
+ 1. [Google Tag Manager](https://tagmanager.google.com/) にサインインし、対象サイトのワークスペースを選択。
55
+ 2. 画面右上にコンテナ ID(`GTM-XXXXXXX` の形式)が表示されます。
56
+ 3. それを上の `containerId` に設定するか、管理画面のフォームに貼り付けます。
57
+
58
+ `GTM-XXXXXXX` はコンテナ識別子であって書き込み認証には使われないため、ソースコードにコミットして問題ありません。
59
+
60
+ ### ID パターンについて
61
+
62
+ admin form はコンテナ ID を `^$|^GTM-[A-Z0-9]+$` でバリデートします(空、または「`GTM-` で始まりその後英数字」の実用形)。Google の [公式インストールドキュメント](https://support.google.com/tagmanager/answer/14847097) は厳密フォーマットを明文化していないため、これは意図的に緩い sanity check です。Google が今後より広い文字集合を使い始めたら issue を立ててください、パターンを広げます。
63
+
64
+ ## 複数インスタンス
65
+
66
+ 各 `gtmPlugin(...)` 呼び出しは描画 DOM と admin 設定保存の両方で独立した `instanceId` namespace を持ちます:
67
+
68
+ ```ts
69
+ plugins: [
70
+ gtmPlugin({ instanceId: 'marketing', containerId: 'GTM-AAA' }),
71
+ gtmPlugin({ instanceId: 'product', containerId: 'GTM-BBB' }),
72
+ ]
73
+ ```
74
+
75
+ admin form では各 instance が個別パネルとして表示されます。
76
+
77
+ ## トラストレベル
78
+
79
+ `untrusted`。プラグインは `@ampless/runtime` が検証・描画する head / body descriptor を返すだけです。DynamoDB、S3、Lambda プロセッサーには一切触れません。
80
+
81
+ ## まだやらないこと
82
+
83
+ - **CSP nonce 連携** — インラインローダー script は `nonce` 無しで emit されます。ampless サイトに CSP enforcement を入れる段で別 RFP として middleware → SSR → descriptor の通り道を設計します
84
+ - **GTM コンテナのインポート** — このプラグインはローダーを注入するだけです。tags / triggers / variables は通常のサイトと同じく GTM の Web UI 内で設定してください
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ > 日本語版: [README.ja.md](./README.ja.md)
2
+ >
3
+
4
+ # @ampless/plugin-gtm
5
+
6
+ Google Tag Manager plugin for [ampless](https://github.com/heavymoons/ampless).
7
+
8
+ > **Pre-release / alpha.** Breaking changes possible in any minor version until v1.0.
9
+
10
+ Drops Google Tag Manager's standard two-part snippet onto every public page through the descriptor-based plugin head/body injection API:
11
+
12
+ 1. The async loader inline `<script>` in `<head>`.
13
+ 2. The matching `<noscript>` iframe at the end of `<body>`, so visitors with JavaScript disabled still register a pageview through GTM.
14
+
15
+ The container ID is **editable from `/admin/plugins`** after deploy — the constructor argument in `cms.config.ts` just seeds the initial default. No AWS data permissions are required; the plugin's `trust_level` is `untrusted` and everything runs at request time inside the public Next.js process.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install @ampless/plugin-gtm@alpha
21
+ ```
22
+
23
+ ## Configure
24
+
25
+ In `cms.config.ts`:
26
+
27
+ ```ts
28
+ import { defineConfig } from 'ampless'
29
+ import gtmPlugin from '@ampless/plugin-gtm'
30
+
31
+ export default defineConfig({
32
+ // ...
33
+ plugins: [
34
+ gtmPlugin({
35
+ // Initial container ID. Editable from /admin/plugins after deploy.
36
+ // Leave empty to ship the plugin disabled and turn it on later.
37
+ containerId: '',
38
+ }),
39
+ ],
40
+ })
41
+ ```
42
+
43
+ | Option | Default | Notes |
44
+ |---|---|---|
45
+ | `containerId` | `''` | Initial GTM container ID, e.g. `GTM-XXXXXXX`. Seeds the manifest default — the live value is read from `/admin/plugins` at request time. Set to `''` to ship the plugin disabled. |
46
+ | `instanceId` | `'gtm'` | Namespace used for the script / noscript element ids and the settings storage key. Set distinct values when registering multiple GTM containers on the same site. |
47
+
48
+ ## Editing the container ID from the admin UI
49
+
50
+ After a deploy, the container ID lives at `/admin/plugins` → **Google Tag Manager**. Saving an empty value disables the plugin without removing it from `cms.config.ts`; saving `GTM-XXXXXXX` enables it. Changes are reflected on the public site after the site-settings S3 mirror finishes (a few seconds; the admin form delays the cache invalidation slightly so the rebuild has time to complete).
51
+
52
+ ## Getting your container ID
53
+
54
+ 1. Sign in to [Google Tag Manager](https://tagmanager.google.com/) and pick the workspace for the site.
55
+ 2. The container ID (`GTM-XXXXXXX`) is shown in the top-right of the workspace UI.
56
+ 3. Copy it into the `containerId` option above, or paste it into the admin form.
57
+
58
+ `GTM-XXXXXXX` is safe to commit to source control — it identifies the container but does not authenticate writes.
59
+
60
+ ### A note on the ID pattern
61
+
62
+ The admin form validates the container ID against `^$|^GTM-[A-Z0-9]+$` — empty or the practical "starts with `GTM-`, then alphanumeric" shape. Google's [official install docs](https://support.google.com/tagmanager/answer/14847097) don't publish a strict format, so this is intentionally a loose sanity-check rather than a tight schema. If Google starts issuing a wider character set, file an issue and we'll widen the pattern.
63
+
64
+ ## Multiple instances
65
+
66
+ Each `gtmPlugin(...)` call gets its own `instanceId` namespace, both in the rendered DOM and in the admin settings storage:
67
+
68
+ ```ts
69
+ plugins: [
70
+ gtmPlugin({ instanceId: 'marketing', containerId: 'GTM-AAA' }),
71
+ gtmPlugin({ instanceId: 'product', containerId: 'GTM-BBB' }),
72
+ ]
73
+ ```
74
+
75
+ The admin form lists each instance as its own panel.
76
+
77
+ ## Trust level
78
+
79
+ `untrusted`. The plugin only contributes head and body descriptors that are validated and rendered by `@ampless/runtime`. It does not touch DynamoDB, S3, or any Lambda processor.
80
+
81
+ ## What it does not do
82
+
83
+ - **CSP nonce propagation** — the inline loader script is emitted without a `nonce`. ampless sites don't enforce a CSP today; once that lands, a dedicated RFP will wire `nonce` end-to-end (middleware → SSR → descriptor).
84
+ - **GTM container imports** — this plugin only injects the loader. Tags / triggers / variables are configured inside the GTM web UI like any other site.
@@ -0,0 +1,39 @@
1
+ import { AmplessPlugin } from 'ampless';
2
+
3
+ interface GtmOptions {
4
+ /**
5
+ * Optional fallback Google Tag Manager container ID, e.g.
6
+ * `"GTM-XXXXXXX"`. Used only when no admin-stored value exists in
7
+ * `pk='siteconfig', sk='plugins.<instanceId>.containerId'`. An
8
+ * empty string disables the plugin (both `publicHead` and
9
+ * `publicBodyEnd` return `[]`), which is the recommended way to
10
+ * keep the dependency wired up while toggling GTM off — for
11
+ * example in staging or before consent is granted.
12
+ *
13
+ * New deployments should leave it empty and configure the value
14
+ * from `/admin/plugins`.
15
+ */
16
+ containerId?: string;
17
+ /**
18
+ * Optional namespace for this instance. Defaults to `'gtm'`. Set
19
+ * distinct values when registering the plugin twice (e.g. a
20
+ * marketing + a product container on the same site).
21
+ */
22
+ instanceId?: string;
23
+ }
24
+ /**
25
+ * Factory for the GTM plugin. Returns a plugin manifest that emits
26
+ * the GTM loader inline script in `<head>` and the matching
27
+ * `<noscript>` iframe at the end of `<body>`. The plugin is
28
+ * `untrusted` — it does not need any AWS data permissions because
29
+ * everything happens inside the public Next.js process at render
30
+ * time.
31
+ *
32
+ * The container ID is read at request time through
33
+ * `ctx.setting<string>('containerId')`, with the constructor
34
+ * argument seeding the manifest `default` for first-time installs
35
+ * before an admin has saved a value.
36
+ */
37
+ declare function gtmPlugin(options?: GtmOptions): AmplessPlugin;
38
+
39
+ export { type GtmOptions, gtmPlugin as default };
package/dist/index.js ADDED
@@ -0,0 +1,70 @@
1
+ // src/index.ts
2
+ import { definePlugin } from "ampless";
3
+ function gtmPlugin(options = {}) {
4
+ const { containerId = "", instanceId = "gtm" } = options;
5
+ return definePlugin({
6
+ name: "gtm",
7
+ instanceId,
8
+ displayName: { en: "Google Tag Manager", ja: "Google Tag Manager" },
9
+ apiVersion: 1,
10
+ trust_level: "untrusted",
11
+ capabilities: ["publicHead", "publicBody", "adminSettings"],
12
+ settings: {
13
+ public: [
14
+ {
15
+ type: "text",
16
+ key: "containerId",
17
+ label: {
18
+ en: "Container ID",
19
+ ja: "\u30B3\u30F3\u30C6\u30CA ID"
20
+ },
21
+ description: {
22
+ en: "GTM container ID (e.g. GTM-XXXXXXX). Leave blank to disable the plugin without removing it from cms.config.",
23
+ ja: "GTM \u306E\u30B3\u30F3\u30C6\u30CA ID (\u4F8B: GTM-XXXXXXX)\u3002\u7A7A\u306B\u3059\u308B\u3068 cms.config \u304B\u3089\u524A\u9664\u305B\u305A\u306B\u30D7\u30E9\u30B0\u30A4\u30F3\u3092\u7121\u52B9\u5316\u3067\u304D\u307E\u3059\u3002"
24
+ },
25
+ // Accept either empty (disable) or a well-formed GTM
26
+ // container id. Google's install docs don't publish a
27
+ // strict format, so we keep this loose — empty + the
28
+ // common `GTM-XXXX...` shape is the practical check.
29
+ pattern: "^$|^GTM-[A-Z0-9]+$",
30
+ placeholder: "GTM-XXXXXXX",
31
+ default: containerId
32
+ }
33
+ ]
34
+ },
35
+ publicHead(ctx) {
36
+ const id = (ctx.setting("containerId") ?? "").trim();
37
+ if (!id) return [];
38
+ return [
39
+ {
40
+ type: "inlineScript",
41
+ id: `gtm-loader-${instanceId}`,
42
+ strategy: "afterInteractive",
43
+ // GTM's official "Add this code to the <head>" snippet,
44
+ // minified. JSON.stringify the container ID so any
45
+ // accidental quote/control char would stay quoted instead
46
+ // of breaking out of the literal.
47
+ body: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer',${JSON.stringify(id)});`
48
+ }
49
+ ];
50
+ },
51
+ publicBodyEnd(ctx) {
52
+ const id = (ctx.setting("containerId") ?? "").trim();
53
+ if (!id) return [];
54
+ return [
55
+ {
56
+ type: "noscript",
57
+ id: `gtm-noscript-${instanceId}`,
58
+ // The matching `<noscript>` iframe fallback. The runtime
59
+ // pipes `html` through `dangerouslySetInnerHTML`, which is
60
+ // safe here because we built the value from a
61
+ // validator-checked container ID + a fixed template.
62
+ html: `<iframe src="https://www.googletagmanager.com/ns.html?id=${encodeURIComponent(id)}" height="0" width="0" style="display:none;visibility:hidden"></iframe>`
63
+ }
64
+ ];
65
+ }
66
+ });
67
+ }
68
+ export {
69
+ gtmPlugin as default
70
+ };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@ampless/plugin-gtm",
3
+ "version": "0.1.1-alpha.0",
4
+ "description": "Google Tag Manager plugin for ampless",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "README.md"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/heavymoons/ampless.git",
23
+ "directory": "packages/plugin-gtm"
24
+ },
25
+ "homepage": "https://github.com/heavymoons/ampless/tree/main/packages/plugin-gtm#readme",
26
+ "bugs": "https://github.com/heavymoons/ampless/issues",
27
+ "dependencies": {
28
+ "ampless": "1.0.0-alpha.18"
29
+ },
30
+ "keywords": [
31
+ "ampless",
32
+ "plugin",
33
+ "gtm",
34
+ "google-tag-manager",
35
+ "analytics"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsup",
39
+ "dev": "tsup --watch",
40
+ "lint": "tsc --noEmit",
41
+ "test": "vitest run --passWithNoTests"
42
+ }
43
+ }