diff --git a/packages/post-kit-publisher/LICENSE b/packages/post-kit-publisher/LICENSE
new file mode 100644
index 0000000..c8b783b
--- /dev/null
+++ b/packages/post-kit-publisher/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Singleton SD
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/post-kit-publisher/README.md b/packages/post-kit-publisher/README.md
new file mode 100644
index 0000000..b7b0972
--- /dev/null
+++ b/packages/post-kit-publisher/README.md
@@ -0,0 +1,91 @@
+# @singleton-sd/post-kit-publisher
+
+Compile Git-backed PostKit email templates and publish runtime artifacts to Azure Blob Storage for `apps/api` TemplateStore.
+
+## Install
+
+```bash
+pnpm add @singleton-sd/post-kit-publisher
+```
+
+## CLI
+
+```bash
+post-kit-publish \
+ --templates ./content/email-templates \
+ --tenant inkads \
+ --environment production \
+ --storage-account ssdpostkitstprodae \
+ --container templates \
+ --commit "$GITHUB_SHA"
+```
+
+Auth uses `DefaultAzureCredential` (Managed Identity in CI, `az login` locally). No connection strings.
+
+Blob layout (must match TemplateStore):
+
+```text
+tenants/{tenant}/{environment}/templates/{templateKey}/template.html
+tenants/{tenant}/{environment}/templates/{templateKey}/metadata.json
+```
+
+If any template fails to compile, the CLI exits non-zero and uploads nothing.
+
+## Library
+
+```ts
+import { publishTemplates } from '@singleton-sd/post-kit-publisher';
+
+const result = await publishTemplates({
+ templatesDir: './content/email-templates',
+ tenant: 'inkads',
+ environment: 'production',
+ storageAccount: 'ssdpostkitstprodae',
+ container: 'templates',
+ commit: process.env.GITHUB_SHA,
+});
+```
+
+## GitHub Actions (OIDC) example
+
+```yaml
+name: Publish email templates
+on:
+ push:
+ paths:
+ - 'content/email-templates/**'
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write
+ contents: read
+ steps:
+ - uses: actions/checkout@v4
+ - uses: pnpm/action-setup@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ cache: pnpm
+ - run: pnpm install --frozen-lockfile
+ - uses: azure/login@v2
+ with:
+ client-id: ${{ vars.AZURE_CLIENT_ID }}
+ tenant-id: ${{ vars.AZURE_TENANT_ID }}
+ subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
+ - run: >
+ pnpm exec post-kit-publish
+ --templates ./content/email-templates
+ --tenant inkads
+ --environment production
+ --storage-account ssdpostkitstprodae
+ --container templates
+ --commit ${{ github.sha }}
+```
+
+## Development
+
+```bash
+pnpm test
+pnpm build
+```
diff --git a/packages/post-kit-publisher/package.json b/packages/post-kit-publisher/package.json
new file mode 100644
index 0000000..6f189f5
--- /dev/null
+++ b/packages/post-kit-publisher/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "@singleton-sd/post-kit-publisher",
+ "version": "0.1.0",
+ "private": false,
+ "description": "Compile and publish PostKit email templates to Azure Blob Storage",
+ "license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ },
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "bin": {
+ "post-kit-publish": "./dist/bin/post-kit-publish.js"
+ },
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.js"
+ }
+ },
+ "files": [
+ "dist",
+ "LICENSE"
+ ],
+ "scripts": {
+ "build": "pnpm --filter @singleton-sd/post-kit-types run build && pnpm --filter @singleton-sd/post-kit-compiler run build && tsc -p tsconfig.json",
+ "lint": "echo \"lint:publisher — covered by root eslint on staged files\"",
+ "test": "pnpm --filter @singleton-sd/post-kit-types run build && pnpm --filter @singleton-sd/post-kit-compiler run build && tsc -p tsconfig.spec.json && node --import tsx --test src/**/*.spec.ts"
+ },
+ "devDependencies": {
+ "@types/node": "^20.17.9",
+ "tsx": "^4.19.2",
+ "typescript": "^5.7.2"
+ },
+ "engines": {
+ "node": ">=20.18.1"
+ },
+ "dependencies": {
+ "@azure/identity": "^4.13.1",
+ "@azure/storage-blob": "^12.33.0",
+ "@singleton-sd/post-kit-compiler": "workspace:*",
+ "@singleton-sd/post-kit-types": "workspace:*"
+ }
+}
diff --git a/packages/post-kit-publisher/src/bin/post-kit-publish.ts b/packages/post-kit-publisher/src/bin/post-kit-publish.ts
new file mode 100644
index 0000000..58d55aa
--- /dev/null
+++ b/packages/post-kit-publisher/src/bin/post-kit-publish.ts
@@ -0,0 +1,60 @@
+#!/usr/bin/env node
+import { publishTemplates } from '../publish';
+import type { TenantEnvironment } from '@singleton-sd/post-kit-types';
+
+function usage(): never {
+ console.error(`Usage:
+ post-kit-publish \\
+ --templates
\\
+ --tenant \\
+ --environment \\
+ --storage-account \\
+ --container \\
+ [--commit ]`);
+ process.exit(2);
+}
+
+function readFlag(argv: string[], name: string): string | undefined {
+ const idx = argv.indexOf(name);
+ if (idx === -1) return undefined;
+ return argv[idx + 1];
+}
+
+async function main(): Promise {
+ const argv = process.argv.slice(2);
+ if (argv.includes('--help') || argv.includes('-h')) usage();
+
+ const templates = readFlag(argv, '--templates');
+ const tenant = readFlag(argv, '--tenant');
+ const environment = readFlag(argv, '--environment');
+ const storageAccount = readFlag(argv, '--storage-account');
+ const container = readFlag(argv, '--container');
+ const commit = readFlag(argv, '--commit');
+
+ if (!templates || !tenant || !environment || !storageAccount || !container) {
+ usage();
+ }
+
+ const result = await publishTemplates({
+ templatesDir: templates,
+ tenant,
+ environment: environment as TenantEnvironment,
+ storageAccount,
+ container,
+ commit,
+ });
+
+ if (result.failed.length > 0) {
+ for (const failure of result.failed) {
+ console.error(`FAILED ${failure.key}: ${failure.error}`);
+ }
+ process.exit(1);
+ }
+
+ console.error(`Published ${result.published.length} template(s): ${result.published.join(', ')}`);
+}
+
+main().catch((err: unknown) => {
+ console.error(err instanceof Error ? err.message : err);
+ process.exit(1);
+});
diff --git a/packages/post-kit-publisher/src/index.ts b/packages/post-kit-publisher/src/index.ts
new file mode 100644
index 0000000..658fee7
--- /dev/null
+++ b/packages/post-kit-publisher/src/index.ts
@@ -0,0 +1,8 @@
+export { publishTemplates, type PublishOptions, type PublishResult } from './publish';
+export {
+ assertSafeTenantId,
+ assertSafeEnvironment,
+ assertSafeTemplateKey,
+ assertSafeStorageAccount,
+ blobBasePath,
+} from './path-safety';
diff --git a/packages/post-kit-publisher/src/path-safety.ts b/packages/post-kit-publisher/src/path-safety.ts
new file mode 100644
index 0000000..183c49b
--- /dev/null
+++ b/packages/post-kit-publisher/src/path-safety.ts
@@ -0,0 +1,55 @@
+import type { TenantEnvironment } from '@singleton-sd/post-kit-types';
+
+const SAFE_SEGMENT = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
+const SAFE_TEMPLATE_KEY = /^[a-zA-Z0-9._-]+$/;
+/** Azure Storage account names: 3–24 lowercase letters and digits. */
+const SAFE_STORAGE_ACCOUNT = /^[a-z0-9]{3,24}$/;
+const ENVIRONMENTS = new Set(['development', 'staging', 'production']);
+
+export function assertSafeTenantId(tenant: string): void {
+ if (!tenant || !SAFE_SEGMENT.test(tenant) || tenant.includes('..')) {
+ throw new Error(
+ `Invalid tenant "${tenant}". Use alphanumeric characters and hyphens only (no path segments).`,
+ );
+ }
+}
+
+export function assertSafeEnvironment(
+ environment: string,
+): asserts environment is TenantEnvironment {
+ if (!ENVIRONMENTS.has(environment as TenantEnvironment)) {
+ throw new Error(
+ `Invalid environment "${environment}". Must be one of: development, staging, production.`,
+ );
+ }
+}
+
+export function assertSafeTemplateKey(templateKey: string): void {
+ if (
+ !templateKey ||
+ templateKey === '.' ||
+ templateKey === '..' ||
+ !SAFE_TEMPLATE_KEY.test(templateKey)
+ ) {
+ throw new Error(
+ `Invalid template key "${templateKey}". Must match /^[a-zA-Z0-9._-]+$/ and must not be "." or "..".`,
+ );
+ }
+}
+
+export function assertSafeStorageAccount(storageAccount: string): void {
+ if (!SAFE_STORAGE_ACCOUNT.test(storageAccount)) {
+ throw new Error(`Invalid storage account "${storageAccount}". Must match /^[a-z0-9]{3,24}$/.`);
+ }
+}
+
+export function blobBasePath(
+ tenant: string,
+ environment: TenantEnvironment,
+ templateKey: string,
+): string {
+ assertSafeTenantId(tenant);
+ assertSafeEnvironment(environment);
+ assertSafeTemplateKey(templateKey);
+ return `tenants/${tenant}/${environment}/templates/${templateKey}`;
+}
diff --git a/packages/post-kit-publisher/src/publish.spec.ts b/packages/post-kit-publisher/src/publish.spec.ts
new file mode 100644
index 0000000..ae3b653
--- /dev/null
+++ b/packages/post-kit-publisher/src/publish.spec.ts
@@ -0,0 +1,166 @@
+import assert from 'node:assert/strict';
+import { mkdtemp, writeFile, cp } from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+import { join } from 'node:path';
+import { describe, it } from 'node:test';
+import type { BlobServiceClient, ContainerClient, BlockBlobClient } from '@azure/storage-blob';
+import {
+ assertSafeEnvironment,
+ assertSafeStorageAccount,
+ assertSafeTenantId,
+ assertSafeTemplateKey,
+ blobBasePath,
+} from './path-safety';
+import { publishTemplatesWithClient } from './publish';
+
+const FIXTURES = join(import.meta.dirname, '../../post-kit-compiler/src/fixtures');
+
+describe('path safety', () => {
+ it('rejects unsafe tenant ids', () => {
+ assert.throws(() => assertSafeTenantId('../x'));
+ assert.throws(() => assertSafeTenantId(''));
+ assert.throws(() => assertSafeTenantId('a/b'));
+ assert.doesNotThrow(() => assertSafeTenantId('inkads'));
+ });
+
+ it('rejects invalid environments', () => {
+ assert.throws(() => assertSafeEnvironment('prod'));
+ assert.doesNotThrow(() => assertSafeEnvironment('production'));
+ });
+
+ it('rejects unsafe template keys', () => {
+ assert.throws(() => assertSafeTemplateKey('..'));
+ assert.throws(() => assertSafeTemplateKey('.'));
+ assert.throws(() => assertSafeTemplateKey('a/b'));
+ assert.doesNotThrow(() => assertSafeTemplateKey('marketing.contact-us'));
+ });
+
+ it('rejects unsafe storage account names', () => {
+ assert.throws(() => assertSafeStorageAccount('attacker.example/'));
+ assert.throws(() => assertSafeStorageAccount('Ab'));
+ assert.throws(() => assertSafeStorageAccount('UPPERCASEACCOUNT'));
+ assert.doesNotThrow(() => assertSafeStorageAccount('ssdpostkitstprodae'));
+ });
+
+ it('builds the TemplateStore blob base path', () => {
+ assert.equal(
+ blobBasePath('inkads', 'production', 'marketing.contact-us'),
+ 'tenants/inkads/production/templates/marketing.contact-us',
+ );
+ });
+});
+
+describe('publishTemplates', () => {
+ it('does not upload when compilation fails', async () => {
+ const root = await mkdtemp(join(tmpdir(), 'post-kit-publish-'));
+ await cp(join(FIXTURES, 'malformed-metadata'), join(root, 'bad'), { recursive: true });
+ await writeFile(
+ join(root, 'bad', 'template.json'),
+ JSON.stringify({ root: { type: 'EmailLayout', data: { childrenIds: [] } } }),
+ );
+ await writeFile(join(root, 'bad', 'preview.json'), '{}');
+
+ let uploads = 0;
+ const client = makeFakeClient(() => {
+ uploads += 1;
+ });
+
+ const result = await publishTemplatesWithClient(
+ {
+ templatesDir: root,
+ tenant: 'inkads',
+ environment: 'development',
+ storageAccount: 'ssdpostkitstprodae',
+ container: 'templates',
+ },
+ client,
+ );
+
+ assert.equal(result.published.length, 0);
+ assert.ok(result.failed.length >= 1);
+ assert.equal(uploads, 0);
+ });
+
+ it('does not upload when two directories share the same template key', async () => {
+ const root = await mkdtemp(join(tmpdir(), 'post-kit-publish-'));
+ await cp(join(FIXTURES, 'marketing.contact-us'), join(root, 'copy-a'), { recursive: true });
+ await cp(join(FIXTURES, 'marketing.contact-us'), join(root, 'copy-b'), { recursive: true });
+
+ let uploads = 0;
+ const client = makeFakeClient(() => {
+ uploads += 1;
+ });
+
+ const result = await publishTemplatesWithClient(
+ {
+ templatesDir: root,
+ tenant: 'inkads',
+ environment: 'production',
+ storageAccount: 'ssdpostkitstprodae',
+ container: 'templates',
+ },
+ client,
+ );
+
+ assert.equal(result.published.length, 0);
+ assert.ok(result.failed.some((f) => f.error.includes('Duplicate template key')));
+ assert.equal(uploads, 0);
+ });
+
+ it('uploads template.html and metadata.json for a valid fixture', async () => {
+ const root = await mkdtemp(join(tmpdir(), 'post-kit-publish-'));
+ await cp(join(FIXTURES, 'marketing.contact-us'), join(root, 'marketing.contact-us'), {
+ recursive: true,
+ });
+
+ const uploaded = new Map();
+ const client = makeFakeClient((path, body) => {
+ uploaded.set(path, body);
+ });
+
+ const result = await publishTemplatesWithClient(
+ {
+ templatesDir: root,
+ tenant: 'inkads',
+ environment: 'production',
+ storageAccount: 'ssdpostkitstprodae',
+ container: 'templates',
+ commit: 'abc123',
+ },
+ client,
+ );
+
+ assert.deepEqual(result.published, ['marketing.contact-us']);
+ assert.equal(result.failed.length, 0);
+ assert.ok(
+ uploaded.has('tenants/inkads/production/templates/marketing.contact-us/template.html'),
+ );
+ assert.ok(
+ uploaded.has('tenants/inkads/production/templates/marketing.contact-us/metadata.json'),
+ );
+ const meta = JSON.parse(
+ uploaded.get('tenants/inkads/production/templates/marketing.contact-us/metadata.json')!,
+ );
+ assert.equal(meta.key, 'marketing.contact-us');
+ const html = uploaded.get(
+ 'tenants/inkads/production/templates/marketing.contact-us/template.html',
+ )!;
+ assert.ok(html.includes(' 0);
+ });
+});
+
+function makeFakeClient(onUpload: (path: string, body: string) => void): BlobServiceClient {
+ const getBlockBlobClient = (blobPath: string): BlockBlobClient =>
+ ({
+ upload: async (body: string | Buffer) => {
+ const text = typeof body === 'string' ? body : body.toString('utf8');
+ onUpload(blobPath, text);
+ return {};
+ },
+ }) as unknown as BlockBlobClient;
+
+ const getContainerClient = (): ContainerClient =>
+ ({ getBlockBlobClient }) as unknown as ContainerClient;
+
+ return { getContainerClient } as unknown as BlobServiceClient;
+}
diff --git a/packages/post-kit-publisher/src/publish.ts b/packages/post-kit-publisher/src/publish.ts
new file mode 100644
index 0000000..e718bf4
--- /dev/null
+++ b/packages/post-kit-publisher/src/publish.ts
@@ -0,0 +1,155 @@
+import { readdir, readFile } from 'node:fs/promises';
+import { join } from 'node:path';
+import { BlobServiceClient } from '@azure/storage-blob';
+import { DefaultAzureCredential } from '@azure/identity';
+import { compileFromDirectory } from '@singleton-sd/post-kit-compiler';
+import type { CompiledTemplate, TenantEnvironment } from '@singleton-sd/post-kit-types';
+import {
+ assertSafeEnvironment,
+ assertSafeStorageAccount,
+ assertSafeTenantId,
+ assertSafeTemplateKey,
+ blobBasePath,
+} from './path-safety';
+
+export interface PublishOptions {
+ /** Root directory; each subdirectory is one template. */
+ templatesDir: string;
+ tenant: string;
+ environment: TenantEnvironment;
+ storageAccount: string;
+ container: string;
+ /** Passed through to TemplateManifest.sourceCommit. */
+ commit?: string;
+}
+
+export interface PublishResult {
+ published: string[];
+ failed: Array<{ key: string; error: string }>;
+}
+
+/** Internal batch row — not part of the public package API. */
+interface CompiledEntry {
+ dirName: string;
+ compiled: CompiledTemplate;
+}
+
+/**
+ * Compile every template under `templatesDir`, then upload artifacts.
+ *
+ * Fail-fast for publishing: if any compile fails, nothing is uploaded.
+ * Storage auth always uses `DefaultAzureCredential` (Managed Identity / az login).
+ */
+export async function publishTemplates(options: PublishOptions): Promise {
+ assertSafeTenantId(options.tenant);
+ assertSafeEnvironment(options.environment);
+ assertSafeStorageAccount(options.storageAccount);
+
+ const client = new BlobServiceClient(
+ `https://${options.storageAccount}.blob.core.windows.net`,
+ new DefaultAzureCredential(),
+ );
+ return runPublish(options, client);
+}
+
+/**
+ * Test-only seam that injects a Blob client. Not re-exported from the package root;
+ * package `exports` only expose `.` so consumers cannot import this via the public API.
+ */
+export async function publishTemplatesWithClient(
+ options: PublishOptions,
+ client: BlobServiceClient,
+): Promise {
+ assertSafeTenantId(options.tenant);
+ assertSafeEnvironment(options.environment);
+ assertSafeStorageAccount(options.storageAccount);
+ return runPublish(options, client);
+}
+
+async function runPublish(
+ options: PublishOptions,
+ client: BlobServiceClient,
+): Promise {
+ const entries = await listTemplateDirs(options.templatesDir);
+ const compiled: CompiledEntry[] = [];
+ const failed: PublishResult['failed'] = [];
+ const seenKeys = new Set();
+
+ for (const dirName of entries) {
+ const dir = join(options.templatesDir, dirName);
+ try {
+ const result = await compileFromDirectory(dir, { sourceCommit: options.commit ?? '' });
+ assertSafeTemplateKey(result.metadata.key);
+ if (seenKeys.has(result.metadata.key)) {
+ failed.push({
+ key: dirName,
+ error: `Duplicate template key "${result.metadata.key}" (already compiled from another directory).`,
+ });
+ continue;
+ }
+ seenKeys.add(result.metadata.key);
+ compiled.push({ dirName, compiled: result });
+ } catch (err) {
+ failed.push({
+ key: dirName,
+ error: err instanceof Error ? err.message : String(err),
+ });
+ }
+ }
+
+ if (failed.length > 0) {
+ return { published: [], failed };
+ }
+
+ const containerClient = client.getContainerClient(options.container);
+ const published: string[] = [];
+
+ for (const entry of compiled) {
+ const key = entry.compiled.metadata.key;
+ const base = blobBasePath(options.tenant, options.environment, key);
+ const htmlPath = `${base}/template.html`;
+ const metaPath = `${base}/metadata.json`;
+
+ await containerClient
+ .getBlockBlobClient(htmlPath)
+ .upload(entry.compiled.templateHtml, Buffer.byteLength(entry.compiled.templateHtml, 'utf8'), {
+ blobHTTPHeaders: { blobContentType: 'text/html; charset=utf-8' },
+ });
+ const metadataJson = JSON.stringify(entry.compiled.metadata, null, 2);
+ await containerClient
+ .getBlockBlobClient(metaPath)
+ .upload(metadataJson, Buffer.byteLength(metadataJson, 'utf8'), {
+ blobHTTPHeaders: { blobContentType: 'application/json; charset=utf-8' },
+ });
+
+ published.push(key);
+ console.log(
+ JSON.stringify({
+ key,
+ contentHash: entry.compiled.manifest.contentHash,
+ templateHtml: htmlPath,
+ metadataJson: metaPath,
+ }),
+ );
+ }
+
+ return { published, failed: [] };
+}
+
+async function listTemplateDirs(templatesDir: string): Promise {
+ const names = await readdir(templatesDir, { withFileTypes: true });
+ const dirs = names.filter((d) => d.isDirectory()).map((d) => d.name);
+ dirs.sort();
+
+ for (const name of dirs) {
+ for (const file of ['template.json', 'metadata.json', 'preview.json'] as const) {
+ try {
+ await readFile(join(templatesDir, name, file));
+ } catch {
+ throw new Error(`Template directory "${name}" is missing required file ${file}`);
+ }
+ }
+ }
+
+ return dirs;
+}
diff --git a/packages/post-kit-publisher/tsconfig.json b/packages/post-kit-publisher/tsconfig.json
new file mode 100644
index 0000000..7989add
--- /dev/null
+++ b/packages/post-kit-publisher/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "declaration": true,
+ "removeComments": true,
+ "target": "ES2021",
+ "sourceMap": true,
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "strict": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "esModuleInterop": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["src/**/*.spec.ts"]
+}
diff --git a/packages/post-kit-publisher/tsconfig.spec.json b/packages/post-kit-publisher/tsconfig.spec.json
new file mode 100644
index 0000000..c4325e5
--- /dev/null
+++ b/packages/post-kit-publisher/tsconfig.spec.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "noEmit": true,
+ "module": "es2022",
+ "moduleResolution": "bundler"
+ },
+ "include": ["src/**/*"],
+ "exclude": []
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a589f4c..b8027ce 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -123,6 +123,31 @@ importers:
specifier: ^5.7.2
version: 5.9.3
+ packages/post-kit-publisher:
+ dependencies:
+ '@azure/identity':
+ specifier: ^4.13.1
+ version: 4.13.2
+ '@azure/storage-blob':
+ specifier: ^12.33.0
+ version: 12.33.0
+ '@singleton-sd/post-kit-compiler':
+ specifier: workspace:*
+ version: link:../post-kit-compiler
+ '@singleton-sd/post-kit-types':
+ specifier: workspace:*
+ version: link:../post-kit-types
+ devDependencies:
+ '@types/node':
+ specifier: ^20.17.9
+ version: 20.19.43
+ tsx:
+ specifier: ^4.19.2
+ version: 4.23.12
+ typescript:
+ specifier: ^5.7.2
+ version: 5.9.3
+
packages/post-kit-types:
devDependencies:
'@types/node':