@knocklabs/cli 0.3.1 → 1.0.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.
Files changed (69) hide show
  1. package/README.md +343 -55
  2. package/dist/commands/branch/delete.js +4 -1
  3. package/dist/commands/branch/merge.js +82 -0
  4. package/dist/commands/channel/list.js +73 -0
  5. package/dist/commands/environment/list.js +73 -0
  6. package/dist/commands/guide/new.js +276 -0
  7. package/dist/commands/guide/open.js +106 -0
  8. package/dist/commands/guide/pull.js +5 -6
  9. package/dist/commands/guide/push.js +1 -1
  10. package/dist/commands/guide/validate.js +1 -1
  11. package/dist/commands/init.js +108 -0
  12. package/dist/commands/layout/new.js +228 -0
  13. package/dist/commands/layout/open.js +106 -0
  14. package/dist/commands/layout/pull.js +5 -6
  15. package/dist/commands/layout/push.js +1 -1
  16. package/dist/commands/layout/validate.js +1 -1
  17. package/dist/commands/message-type/new.js +228 -0
  18. package/dist/commands/message-type/open.js +106 -0
  19. package/dist/commands/message-type/pull.js +5 -6
  20. package/dist/commands/message-type/push.js +1 -1
  21. package/dist/commands/message-type/validate.js +1 -1
  22. package/dist/commands/partial/new.js +274 -0
  23. package/dist/commands/partial/open.js +106 -0
  24. package/dist/commands/partial/pull.js +5 -6
  25. package/dist/commands/partial/push.js +1 -1
  26. package/dist/commands/partial/validate.js +1 -1
  27. package/dist/commands/pull.js +7 -2
  28. package/dist/commands/push.js +6 -4
  29. package/dist/commands/translation/pull.js +1 -1
  30. package/dist/commands/translation/push.js +1 -1
  31. package/dist/commands/translation/validate.js +1 -1
  32. package/dist/commands/workflow/new.js +179 -54
  33. package/dist/commands/workflow/open.js +106 -0
  34. package/dist/commands/workflow/pull.js +6 -8
  35. package/dist/commands/workflow/push.js +1 -1
  36. package/dist/commands/workflow/validate.js +1 -1
  37. package/dist/lib/api-v1.js +23 -2
  38. package/dist/lib/auth.js +1 -1
  39. package/dist/lib/base-command.js +18 -15
  40. package/dist/lib/helpers/project-config.js +158 -0
  41. package/dist/lib/helpers/request.js +1 -2
  42. package/dist/lib/helpers/string.js +4 -4
  43. package/dist/lib/helpers/typegen.js +1 -1
  44. package/dist/lib/marshal/email-layout/generator.js +152 -0
  45. package/dist/lib/marshal/email-layout/helpers.js +6 -9
  46. package/dist/lib/marshal/email-layout/index.js +1 -0
  47. package/dist/lib/marshal/email-layout/writer.js +15 -3
  48. package/dist/lib/marshal/guide/generator.js +163 -0
  49. package/dist/lib/marshal/guide/helpers.js +6 -10
  50. package/dist/lib/marshal/guide/index.js +1 -0
  51. package/dist/lib/marshal/guide/writer.js +5 -9
  52. package/dist/lib/marshal/message-type/generator.js +139 -0
  53. package/dist/lib/marshal/message-type/helpers.js +6 -10
  54. package/dist/lib/marshal/message-type/index.js +1 -0
  55. package/dist/lib/marshal/message-type/writer.js +5 -1
  56. package/dist/lib/marshal/partial/generator.js +159 -0
  57. package/dist/lib/marshal/partial/helpers.js +6 -10
  58. package/dist/lib/marshal/partial/index.js +1 -0
  59. package/dist/lib/marshal/partial/writer.js +3 -0
  60. package/dist/lib/marshal/translation/helpers.js +6 -10
  61. package/dist/lib/marshal/translation/processor.isomorphic.js +4 -4
  62. package/dist/lib/marshal/translation/writer.js +2 -2
  63. package/dist/lib/marshal/workflow/generator.js +175 -19
  64. package/dist/lib/marshal/workflow/helpers.js +7 -10
  65. package/dist/lib/run-context/loader.js +5 -0
  66. package/dist/lib/templates.js +131 -0
  67. package/dist/lib/urls.js +16 -0
  68. package/oclif.manifest.json +1446 -547
  69. package/package.json +11 -9
@@ -16,6 +16,7 @@ const _error = require("../../lib/helpers/error");
16
16
  const _flag = /*#__PURE__*/ _interop_require_wildcard(require("../../lib/helpers/flag"));
17
17
  const _objectisomorphic = require("../../lib/helpers/object.isomorphic");
18
18
  const _page = require("../../lib/helpers/page");
19
+ const _projectconfig = require("../../lib/helpers/project-config");
19
20
  const _request = require("../../lib/helpers/request");
20
21
  const _ux = require("../../lib/helpers/ux");
21
22
  const _workflow = /*#__PURE__*/ _interop_require_wildcard(require("../../lib/marshal/workflow"));
@@ -132,10 +133,12 @@ class WorkflowPull extends _basecommand.default {
132
133
  };
133
134
  return (0, _runcontext.ensureResourceDirForTarget)(resourceDir, target);
134
135
  }
136
+ // Default to knock project config first if present, otherwise cwd.
137
+ const dirCtx = await (0, _projectconfig.resolveResourceDir)(this.projectConfig, "workflow", runCwd);
135
138
  // Not inside any existing workflow directory, which means either create a
136
139
  // new worfklow directory in the cwd, or update it if there is one already.
137
140
  if (workflowKey) {
138
- const dirPath = _nodepath.resolve(runCwd, workflowKey);
141
+ const dirPath = _nodepath.resolve(dirCtx.abspath, workflowKey);
139
142
  const exists = await _workflow.isWorkflowDir(dirPath);
140
143
  return {
141
144
  type: "workflow",
@@ -151,13 +154,8 @@ class WorkflowPull extends _basecommand.default {
151
154
  * Pull all workflows
152
155
  */ async pullAllWorkflows() {
153
156
  const { flags } = this.props;
154
- // TODO: In the future we should default to the knock project config first
155
- // if present, before defaulting to the cwd.
156
- const defaultToCwd = {
157
- abspath: this.runContext.cwd,
158
- exists: true
159
- };
160
- const targetDirCtx = flags["workflows-dir"] || defaultToCwd;
157
+ const workflowsIndexDirCtx = await (0, _projectconfig.resolveResourceDir)(this.projectConfig, "workflow", this.runContext.cwd);
158
+ const targetDirCtx = flags["workflows-dir"] || workflowsIndexDirCtx;
161
159
  const prompt = targetDirCtx.exists ? `Pull latest workflows into ${targetDirCtx.abspath}?\n This will overwrite the contents of this directory.` : `Create a new workflows directory at ${targetDirCtx.abspath}?`;
162
160
  const input = flags.force || await (0, _ux.promptToConfirm)(prompt);
163
161
  if (!input) return;
@@ -83,7 +83,7 @@ class WorkflowPush extends _basecommand.default {
83
83
  async run() {
84
84
  const { flags } = this.props;
85
85
  // 1. First read all workflow directories found for the given command.
86
- const target = await _workflow.ensureValidCommandTarget(this.props, this.runContext);
86
+ const target = await _workflow.ensureValidCommandTarget(this.props, this.runContext, this.projectConfig);
87
87
  const [workflows, readErrors] = await _workflow.readAllForCommandTarget(target, {
88
88
  withExtractedFiles: true
89
89
  });
@@ -80,7 +80,7 @@ function _interop_require_wildcard(obj, nodeInterop) {
80
80
  class WorkflowValidate extends _basecommand.default {
81
81
  async run() {
82
82
  // 1. Read all workflow directories found for the given command.
83
- const target = await _workflow.ensureValidCommandTarget(this.props, this.runContext);
83
+ const target = await _workflow.ensureValidCommandTarget(this.props, this.runContext, this.projectConfig);
84
84
  const [workflows, readErrors] = await _workflow.readAllForCommandTarget(target, {
85
85
  withExtractedFiles: true
86
86
  });
@@ -323,6 +323,13 @@ class ApiV1 {
323
323
  params
324
324
  });
325
325
  }
326
+ async listAllMessageTypes(params) {
327
+ const messageTypes = [];
328
+ for await (const messageType of this.mgmtClient.messageTypes.list(params)){
329
+ messageTypes.push(messageType);
330
+ }
331
+ return messageTypes;
332
+ }
326
333
  async getMessageType({ args, flags }) {
327
334
  const params = (0, _objectisomorphic.prune)({
328
335
  environment: flags.environment,
@@ -425,6 +432,20 @@ class ApiV1 {
425
432
  params
426
433
  });
427
434
  }
435
+ async listAllChannels() {
436
+ const channels = [];
437
+ for await (const channel of this.mgmtClient.channels.list()){
438
+ channels.push(channel);
439
+ }
440
+ return channels;
441
+ }
442
+ async listAllEnvironments() {
443
+ const environments = [];
444
+ for await (const environment of this.mgmtClient.environments.list()){
445
+ environments.push(environment);
446
+ }
447
+ return environments;
448
+ }
428
449
  // By methods:
429
450
  async get(subpath, config) {
430
451
  return this.client.get(`/${API_VERSION}` + subpath, config);
@@ -433,18 +454,18 @@ class ApiV1 {
433
454
  return this.client.put(`/${API_VERSION}` + subpath, data, config);
434
455
  }
435
456
  constructor(sessionContext, config){
457
+ var _ref;
436
458
  var _sessionContext_session;
437
459
  _define_property(this, "client", void 0);
438
460
  _define_property(this, "mgmtClient", void 0);
439
461
  const baseURL = sessionContext.apiOrigin;
440
462
  const token = this.getToken(sessionContext);
441
- var _sessionContext_session_clientId;
442
463
  const headers = {
443
464
  // Used to authenticate the request to the API.
444
465
  Authorization: `Bearer ${token}`,
445
466
  // Used in conjunction with the JWT access token, to allow the OAuth server to
446
467
  // verify the client ID of the OAuth client that issued the access token.
447
- "x-knock-client-id": (_sessionContext_session_clientId = (_sessionContext_session = sessionContext.session) === null || _sessionContext_session === void 0 ? void 0 : _sessionContext_session.clientId) !== null && _sessionContext_session_clientId !== void 0 ? _sessionContext_session_clientId : undefined,
468
+ "x-knock-client-id": (_ref = (_sessionContext_session = sessionContext.session) === null || _sessionContext_session === void 0 ? void 0 : _sessionContext_session.clientId) !== null && _ref !== void 0 ? _ref : undefined,
448
469
  "User-Agent": `${config.userAgent}`
449
470
  };
450
471
  this.client = _axios.default.create({
package/dist/lib/auth.js CHANGED
@@ -198,12 +198,12 @@ async function waitForAccessToken(dashboardUrl, authUrl) {
198
198
  };
199
199
  const browserUrl = `${authorizationEndpoint}?${new URLSearchParams(params).toString()}`;
200
200
  server.on("request", async (req, res)=>{
201
+ var _req_url;
201
202
  if (!clientId || !redirectUri) {
202
203
  res.writeHead(500).end("Something went wrong");
203
204
  cleanupAndReject("something went wrong");
204
205
  return;
205
206
  }
206
- var _req_url;
207
207
  const url = new URL((_req_url = req.url) !== null && _req_url !== void 0 ? _req_url : "/", "http://127.0.0.1");
208
208
  if (url.pathname !== callbackPath) {
209
209
  res.writeHead(404).end("Invalid path");
@@ -12,6 +12,7 @@ const _core = require("@oclif/core");
12
12
  const _apiv1 = /*#__PURE__*/ _interop_require_default(require("./api-v1"));
13
13
  const _auth = /*#__PURE__*/ _interop_require_default(require("./auth"));
14
14
  const _accountfeatures = require("./helpers/account-features");
15
+ const _projectconfig = require("./helpers/project-config");
15
16
  const _runcontext = /*#__PURE__*/ _interop_require_wildcard(require("./run-context"));
16
17
  const _urls = require("./urls");
17
18
  const _userconfig = require("./user-config");
@@ -89,52 +90,54 @@ class BaseCommand extends _core.Command {
89
90
  this.configStore = new _userconfig.UserConfigStore(this.config.configDir);
90
91
  // 1. Load user's config from the config dir, as available.
91
92
  await this.configStore.load();
92
- // 2. Parse flags and args, must come after the user config load.
93
+ // 2. Load project config (knock.json) if available.
94
+ this.projectConfig = await (0, _projectconfig.findAndReadProjectConfig)();
95
+ // 3. Parse flags and args, must come after the user config load.
93
96
  const { args, flags } = await this.parse(this.ctor);
94
97
  this.props = {
95
98
  args: args,
96
99
  flags: flags
97
100
  };
98
- // 3. Build the initial session context.
101
+ // 4. Build the initial session context.
99
102
  this.sessionContext = this.buildSessionContext();
100
- // 4. If the command requires authentication, ensure the session is authenticated.
103
+ // 5. If the command requires authentication, ensure the session is authenticated.
101
104
  if (this.requiresAuth) {
102
105
  this.ensureAuthenticated();
103
106
  }
104
- // 5. If the session context is an OAuth session, refresh the access token.
107
+ // 6. If the session context is an OAuth session, refresh the access token.
105
108
  if (this.sessionContext.type === "oauth") {
106
109
  await this.refreshAccessTokenForSession();
107
110
  }
108
- // 6. Instantiate a knock api client.
111
+ // 7. Instantiate a knock api client.
109
112
  this.apiV1 = new _apiv1.default(this.sessionContext, this.config);
110
- // 7. Verify that required features are enabled for the account.
113
+ // 8. Verify that required features are enabled for the account.
111
114
  const ctor = this.ctor;
112
115
  if (ctor.verifyFeatureEnabled) {
113
116
  await this.verifyFeatureEnabled(ctor.verifyFeatureEnabled);
114
117
  }
115
- // 8. Load the run context of the invoked command.
118
+ // 9. Load the run context of the invoked command.
116
119
  this.runContext = await _runcontext.load(this.id);
117
120
  }
118
121
  buildSessionContext() {
122
+ var _this_props_flags_servicetoken, _this_props_flags_apiorigin;
119
123
  const userConfig = this.configStore.get();
120
124
  const session = userConfig.userSession;
121
125
  // If the user has a session and a service token is not provided, use the session.
122
126
  if (session && !this.props.flags["service-token"]) {
123
- var _this_props_flags_apiorigin;
127
+ var _this_props_flags_apiorigin1;
124
128
  return sessionWithDefaultOrigins({
125
129
  type: "oauth",
126
130
  session,
127
- apiOrigin: (_this_props_flags_apiorigin = this.props.flags["api-origin"]) !== null && _this_props_flags_apiorigin !== void 0 ? _this_props_flags_apiorigin : userConfig.apiOrigin,
131
+ apiOrigin: (_this_props_flags_apiorigin1 = this.props.flags["api-origin"]) !== null && _this_props_flags_apiorigin1 !== void 0 ? _this_props_flags_apiorigin1 : userConfig.apiOrigin,
128
132
  dashboardOrigin: userConfig.dashboardOrigin,
129
133
  authOrigin: userConfig.authOrigin
130
134
  });
131
135
  }
132
- var _this_props_flags_servicetoken, _this_props_flags_apiorigin1;
133
136
  // Otherwise, default to this being a service token session.
134
137
  return sessionWithDefaultOrigins({
135
138
  type: "service",
136
139
  token: (_this_props_flags_servicetoken = this.props.flags["service-token"]) !== null && _this_props_flags_servicetoken !== void 0 ? _this_props_flags_servicetoken : userConfig.serviceToken,
137
- apiOrigin: (_this_props_flags_apiorigin1 = this.props.flags["api-origin"]) !== null && _this_props_flags_apiorigin1 !== void 0 ? _this_props_flags_apiorigin1 : userConfig.apiOrigin,
140
+ apiOrigin: (_this_props_flags_apiorigin = this.props.flags["api-origin"]) !== null && _this_props_flags_apiorigin !== void 0 ? _this_props_flags_apiorigin : userConfig.apiOrigin,
138
141
  dashboardOrigin: userConfig.dashboardOrigin,
139
142
  authOrigin: userConfig.authOrigin
140
143
  });
@@ -156,12 +159,12 @@ class BaseCommand extends _core.Command {
156
159
  async refreshAccessTokenForSession() {
157
160
  // Maybe refresh the access token?
158
161
  try {
162
+ var _ref, _ref1;
159
163
  var _this_sessionContext_session, _this_sessionContext_session1;
160
- var _this_sessionContext_session_clientId, _this_sessionContext_session_refreshToken;
161
164
  const refreshedSession = await _auth.default.refreshAccessToken({
162
165
  authUrl: this.sessionContext.authOrigin,
163
- clientId: (_this_sessionContext_session_clientId = (_this_sessionContext_session = this.sessionContext.session) === null || _this_sessionContext_session === void 0 ? void 0 : _this_sessionContext_session.clientId) !== null && _this_sessionContext_session_clientId !== void 0 ? _this_sessionContext_session_clientId : "",
164
- refreshToken: (_this_sessionContext_session_refreshToken = (_this_sessionContext_session1 = this.sessionContext.session) === null || _this_sessionContext_session1 === void 0 ? void 0 : _this_sessionContext_session1.refreshToken) !== null && _this_sessionContext_session_refreshToken !== void 0 ? _this_sessionContext_session_refreshToken : ""
166
+ clientId: (_ref = (_this_sessionContext_session = this.sessionContext.session) === null || _this_sessionContext_session === void 0 ? void 0 : _this_sessionContext_session.clientId) !== null && _ref !== void 0 ? _ref : "",
167
+ refreshToken: (_ref1 = (_this_sessionContext_session1 = this.sessionContext.session) === null || _this_sessionContext_session1 === void 0 ? void 0 : _this_sessionContext_session1.refreshToken) !== null && _ref1 !== void 0 ? _ref1 : ""
165
168
  });
166
169
  this.debug("Successfully refreshed access token.");
167
170
  // Update the user config to use the new session.
@@ -178,7 +181,7 @@ class BaseCommand extends _core.Command {
178
181
  }
179
182
  }
180
183
  constructor(...args){
181
- super(...args), _define_property(this, "props", void 0), _define_property(this, "apiV1", void 0), _define_property(this, "runContext", void 0), _define_property(this, "sessionContext", void 0), _define_property(this, "configStore", void 0), _define_property(this, "requiresAuth", true);
184
+ super(...args), _define_property(this, "props", void 0), _define_property(this, "apiV1", void 0), _define_property(this, "runContext", void 0), _define_property(this, "sessionContext", void 0), _define_property(this, "configStore", void 0), _define_property(this, "projectConfig", void 0), _define_property(this, "requiresAuth", true);
182
185
  }
183
186
  }
184
187
  _define_property(BaseCommand, "verifyFeatureEnabled", void 0);
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get PROJECT_CONFIG_FILE_NAME () {
13
+ return PROJECT_CONFIG_FILE_NAME;
14
+ },
15
+ get ResourceDirectoriesByType () {
16
+ return ResourceDirectoriesByType;
17
+ },
18
+ get findAndReadProjectConfig () {
19
+ return findAndReadProjectConfig;
20
+ },
21
+ get findProjectConfig () {
22
+ return findProjectConfig;
23
+ },
24
+ get readProjectConfig () {
25
+ return readProjectConfig;
26
+ },
27
+ get resolveKnockDir () {
28
+ return resolveKnockDir;
29
+ },
30
+ get resolveResourceDir () {
31
+ return resolveResourceDir;
32
+ }
33
+ });
34
+ const _nodepath = /*#__PURE__*/ _interop_require_wildcard(require("node:path"));
35
+ const _findup = /*#__PURE__*/ _interop_require_default(require("find-up"));
36
+ const _fsextra = /*#__PURE__*/ _interop_require_wildcard(require("fs-extra"));
37
+ const _zod = require("zod");
38
+ const _fs = require("./fs");
39
+ function _interop_require_default(obj) {
40
+ return obj && obj.__esModule ? obj : {
41
+ default: obj
42
+ };
43
+ }
44
+ function _getRequireWildcardCache(nodeInterop) {
45
+ if (typeof WeakMap !== "function") return null;
46
+ var cacheBabelInterop = new WeakMap();
47
+ var cacheNodeInterop = new WeakMap();
48
+ return (_getRequireWildcardCache = function(nodeInterop) {
49
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
50
+ })(nodeInterop);
51
+ }
52
+ function _interop_require_wildcard(obj, nodeInterop) {
53
+ if (!nodeInterop && obj && obj.__esModule) {
54
+ return obj;
55
+ }
56
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
57
+ return {
58
+ default: obj
59
+ };
60
+ }
61
+ var cache = _getRequireWildcardCache(nodeInterop);
62
+ if (cache && cache.has(obj)) {
63
+ return cache.get(obj);
64
+ }
65
+ var newObj = {
66
+ __proto__: null
67
+ };
68
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
69
+ for(var key in obj){
70
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
71
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
72
+ if (desc && (desc.get || desc.set)) {
73
+ Object.defineProperty(newObj, key, desc);
74
+ } else {
75
+ newObj[key] = obj[key];
76
+ }
77
+ }
78
+ }
79
+ newObj.default = obj;
80
+ if (cache) {
81
+ cache.set(obj, newObj);
82
+ }
83
+ return newObj;
84
+ }
85
+ const PROJECT_CONFIG_FILE_NAME = "knock.json";
86
+ /**
87
+ * Schema for the project configuration file.
88
+ */ const projectConfigSchema = _zod.z.object({
89
+ knockDir: _zod.z.string()
90
+ });
91
+ const findProjectConfig = async ()=>{
92
+ const configPath = await (0, _findup.default)(PROJECT_CONFIG_FILE_NAME);
93
+ return configPath;
94
+ };
95
+ const readProjectConfig = async (configPath)=>{
96
+ const rawConfig = await _fsextra.readJSON(configPath);
97
+ const config = projectConfigSchema.parse(rawConfig);
98
+ return config;
99
+ };
100
+ const resolveKnockDir = async (knockDirFlag, projectConfig)=>{
101
+ if (knockDirFlag) {
102
+ return knockDirFlag;
103
+ }
104
+ if (!projectConfig) {
105
+ return undefined;
106
+ }
107
+ // Project configuration exists, must is invalid. So we error out.
108
+ if ([
109
+ "",
110
+ undefined,
111
+ null
112
+ ].includes(projectConfig.knockDir)) {
113
+ throw new Error("No knock directory specified in the project configuration");
114
+ }
115
+ const abspath = _nodepath.isAbsolute(projectConfig.knockDir) ? projectConfig.knockDir : _nodepath.resolve(process.cwd(), projectConfig.knockDir);
116
+ const exists = await _fsextra.pathExists(abspath);
117
+ if (!exists || !await (0, _fs.isDirectory)(abspath)) {
118
+ throw new Error(`${projectConfig.knockDir} does not exist or is not a directory`);
119
+ }
120
+ return {
121
+ abspath,
122
+ exists
123
+ };
124
+ };
125
+ const findAndReadProjectConfig = async ()=>{
126
+ const configPath = await findProjectConfig();
127
+ if (!configPath) {
128
+ return undefined;
129
+ }
130
+ return readProjectConfig(configPath);
131
+ };
132
+ const ResourceDirectoriesByType = {
133
+ workflow: "workflows",
134
+ guide: "guides",
135
+ partial: "partials",
136
+ email_layout: "layouts",
137
+ message_type: "message-types",
138
+ translation: "translations"
139
+ };
140
+ const resolveResourceDir = async (projectConfig, resourceType, basePath = process.cwd())=>{
141
+ if (!projectConfig) {
142
+ return {
143
+ abspath: basePath,
144
+ exists: true
145
+ };
146
+ }
147
+ const absoluteKnockDir = _nodepath.isAbsolute(projectConfig.knockDir) ? projectConfig.knockDir : _nodepath.resolve(basePath, projectConfig.knockDir);
148
+ const resourceDir = ResourceDirectoriesByType[resourceType];
149
+ const absResourceDirPath = _nodepath.resolve(absoluteKnockDir, resourceDir);
150
+ const exists = await _fsextra.pathExists(absResourceDirPath);
151
+ if (exists && !await (0, _fs.isDirectory)(absResourceDirPath)) {
152
+ throw new Error(`${absResourceDirPath} is not a directory`);
153
+ }
154
+ return {
155
+ abspath: absResourceDirPath,
156
+ exists
157
+ };
158
+ };
@@ -49,14 +49,13 @@ const formatErrorRespMessage = ({ status, data })=>{
49
49
  return message;
50
50
  };
51
51
  const formatMgmtError = (apiError)=>{
52
+ var _apiError_error_message, _apiError_error_errors;
52
53
  if (apiError.status === 500) {
53
54
  return "An internal server error occurred";
54
55
  }
55
- var _apiError_error_message;
56
56
  // Prefer the error message from the error object over
57
57
  // the error message formatted by the Stainless SDK
58
58
  const description = `${(_apiError_error_message = apiError.error.message) !== null && _apiError_error_message !== void 0 ? _apiError_error_message : apiError.message} (status: ${apiError.status})`;
59
- var _apiError_error_errors;
60
59
  const inputErrors = (_apiError_error_errors = apiError.error.errors) !== null && _apiError_error_errors !== void 0 ? _apiError_error_errors : [];
61
60
  if (Array.isArray(inputErrors) && inputErrors.length > 0) {
62
61
  const errs = inputErrors.map((e)=>new _error.JsonDataError(e.message, e.field));
@@ -1,6 +1,4 @@
1
- /*
2
- * Checks if a given string is in a slugified format.
3
- */ "use strict";
1
+ "use strict";
4
2
  Object.defineProperty(exports, "__esModule", {
5
3
  value: true
6
4
  });
@@ -21,7 +19,9 @@ _export(exports, {
21
19
  return slugify;
22
20
  }
23
21
  });
24
- const SLUG_FORMAT_RE = /^[\w-]+$/;
22
+ /*
23
+ * Checks if a given string is in a slugified format.
24
+ */ const SLUG_FORMAT_RE = /^[\w-]+$/;
25
25
  const SLUG_LOWERCASE_FORMAT_RE = /^[\d_a-z-]+$/;
26
26
  const checkSlugifiedFormat = (input, opts = {})=>{
27
27
  const { onlyLowerCase = true } = opts;
@@ -44,10 +44,10 @@ function getLanguageFromExtension(extension) {
44
44
  }
45
45
  }
46
46
  function transformSchema(schema) {
47
+ var _schema_properties;
47
48
  if (schema.type === "object" && !schema.additionalProperties) {
48
49
  schema.additionalProperties = false;
49
50
  }
50
- var _schema_properties;
51
51
  for (const key of Object.keys((_schema_properties = schema.properties) !== null && _schema_properties !== void 0 ? _schema_properties : {})){
52
52
  const property = schema.properties[key];
53
53
  if (property.type === "object") {
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get generateEmailLayoutDir () {
13
+ return generateEmailLayoutDir;
14
+ },
15
+ get generateEmailLayoutFromTemplate () {
16
+ return generateEmailLayoutFromTemplate;
17
+ },
18
+ get scaffoldEmailLayoutDirBundle () {
19
+ return scaffoldEmailLayoutDirBundle;
20
+ },
21
+ get validateEmailLayoutKey () {
22
+ return validateEmailLayoutKey;
23
+ }
24
+ });
25
+ const _lodash = require("lodash");
26
+ const _string = require("../../helpers/string");
27
+ const _templates = /*#__PURE__*/ _interop_require_wildcard(require("../../templates"));
28
+ const _constisomorphic = require("../shared/const.isomorphic");
29
+ const _processorisomorphic = require("./processor.isomorphic");
30
+ const _reader = require("./reader");
31
+ const _writer = require("./writer");
32
+ function _getRequireWildcardCache(nodeInterop) {
33
+ if (typeof WeakMap !== "function") return null;
34
+ var cacheBabelInterop = new WeakMap();
35
+ var cacheNodeInterop = new WeakMap();
36
+ return (_getRequireWildcardCache = function(nodeInterop) {
37
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
38
+ })(nodeInterop);
39
+ }
40
+ function _interop_require_wildcard(obj, nodeInterop) {
41
+ if (!nodeInterop && obj && obj.__esModule) {
42
+ return obj;
43
+ }
44
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
45
+ return {
46
+ default: obj
47
+ };
48
+ }
49
+ var cache = _getRequireWildcardCache(nodeInterop);
50
+ if (cache && cache.has(obj)) {
51
+ return cache.get(obj);
52
+ }
53
+ var newObj = {
54
+ __proto__: null
55
+ };
56
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
57
+ for(var key in obj){
58
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
59
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
60
+ if (desc && (desc.get || desc.set)) {
61
+ Object.defineProperty(newObj, key, desc);
62
+ } else {
63
+ newObj[key] = obj[key];
64
+ }
65
+ }
66
+ }
67
+ newObj.default = obj;
68
+ if (cache) {
69
+ cache.set(obj, newObj);
70
+ }
71
+ return newObj;
72
+ }
73
+ const validateEmailLayoutKey = (input)=>{
74
+ if (!(0, _string.checkSlugifiedFormat)(input, {
75
+ onlyLowerCase: true
76
+ })) {
77
+ return "must include only lowercase alphanumeric, dash, or underscore characters";
78
+ }
79
+ return undefined;
80
+ };
81
+ /*
82
+ * Returns the default scaffolded HTML layout content.
83
+ */ const defaultHtmlLayoutContent = ()=>{
84
+ return `<!DOCTYPE html>
85
+ <html>
86
+ <head>
87
+ <meta charset="utf-8">
88
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
89
+ <title>{{ title }}</title>
90
+ </head>
91
+ <body>
92
+ <div style="max-width: 600px; margin: 0 auto; padding: 20px;">
93
+ {{ content }}
94
+ </div>
95
+ </body>
96
+ </html>`;
97
+ };
98
+ /*
99
+ * Returns the default scaffolded text layout content.
100
+ */ const defaultTextLayoutContent = ()=>{
101
+ return `{{ content }}`;
102
+ };
103
+ /*
104
+ * Scaffolds a new email layout directory bundle with default content.
105
+ */ const scaffoldEmailLayoutDirBundle = (attrs)=>{
106
+ const htmlLayoutFilePath = "html_layout.html";
107
+ const textLayoutFilePath = "text_layout.txt";
108
+ const defaultHtmlContent = defaultHtmlLayoutContent();
109
+ const defaultTextContent = defaultTextLayoutContent();
110
+ const layoutJson = {
111
+ name: attrs.name,
112
+ [`html_layout${_constisomorphic.FILEPATH_MARKER}`]: htmlLayoutFilePath,
113
+ [`text_layout${_constisomorphic.FILEPATH_MARKER}`]: textLayoutFilePath
114
+ };
115
+ return {
116
+ [_processorisomorphic.LAYOUT_JSON]: layoutJson,
117
+ [htmlLayoutFilePath]: defaultHtmlContent,
118
+ [textLayoutFilePath]: defaultTextContent
119
+ };
120
+ };
121
+ const generateEmailLayoutDir = async (emailLayoutDirCtx, attrs)=>{
122
+ const bundle = scaffoldEmailLayoutDirBundle(attrs);
123
+ return (0, _writer.writeEmailLayoutDirFromBundle)(emailLayoutDirCtx, bundle);
124
+ };
125
+ const generateEmailLayoutFromTemplate = async (emailLayoutDirCtx, templateString, attrs)=>{
126
+ let tempDir;
127
+ try {
128
+ // Download the template directory into a temp directory
129
+ tempDir = await _templates.downloadTemplate(templateString);
130
+ // Create an email layout directory context for the temp directory
131
+ const tempEmailLayoutDirCtx = {
132
+ type: "email_layout",
133
+ key: "temp",
134
+ abspath: tempDir,
135
+ exists: true
136
+ };
137
+ // Read the layout.json from the temp directory we downloaded
138
+ const [emailLayout, errors] = await (0, _reader.readEmailLayoutDir)(tempEmailLayoutDirCtx, {
139
+ withExtractedFiles: true
140
+ });
141
+ if (errors.length > 0 || !emailLayout) {
142
+ throw new Error(`Invalid email layout template: ${errors.join(", ")}`);
143
+ }
144
+ // Modify the email layout data with the new attributes
145
+ const emailLayoutData = (0, _lodash.cloneDeep)(emailLayout);
146
+ emailLayoutData.name = attrs.name;
147
+ // Finally, we write the email layout into the target email layout directory
148
+ await (0, _writer.writeEmailLayoutDirFromData)(emailLayoutDirCtx, emailLayoutData);
149
+ } finally{
150
+ await _templates.cleanupTempDir(tempDir);
151
+ }
152
+ };
@@ -25,6 +25,7 @@ _export(exports, {
25
25
  const _nodepath = /*#__PURE__*/ _interop_require_wildcard(require("node:path"));
26
26
  const _core = require("@oclif/core");
27
27
  const _fsextra = /*#__PURE__*/ _interop_require_wildcard(require("fs-extra"));
28
+ const _projectconfig = require("../../helpers/project-config");
28
29
  const _processorisomorphic = require("./processor.isomorphic");
29
30
  function _getRequireWildcardCache(nodeInterop) {
30
31
  if (typeof WeakMap !== "function") return null;
@@ -74,7 +75,7 @@ const lsEmailLayoutJson = async (dirPath)=>{
74
75
  const exists = await _fsextra.pathExists(emailLayoutJsonPath);
75
76
  return exists ? emailLayoutJsonPath : undefined;
76
77
  };
77
- const ensureValidCommandTarget = async (props, runContext)=>{
78
+ const ensureValidCommandTarget = async (props, runContext, projectConfig)=>{
78
79
  const { args, flags } = props;
79
80
  const { commandId, resourceDir: resourceDirCtx, cwd: runCwd } = runContext;
80
81
  // If the target resource is a different type than the current resource dir
@@ -86,19 +87,15 @@ const ensureValidCommandTarget = async (props, runContext)=>{
86
87
  if (flags.all && args.emailLayoutKey) {
87
88
  return _core.ux.error(`emailLayoutKey arg \`${args.emailLayoutKey}\` cannot also be provided when using --all`);
88
89
  }
90
+ // Default to knock project config first if present, otherwise cwd.
91
+ const layoutsIndexDirCtx = await (0, _projectconfig.resolveResourceDir)(projectConfig, "email_layout", runCwd);
89
92
  // --all flag is given, which means no layout key arg.
90
93
  if (flags.all) {
91
94
  // If --all flag used inside a layout directory, then require a layouts dir path.
92
95
  if (resourceDirCtx && !flags["layouts-dir"]) {
93
96
  return _core.ux.error("Missing required flag layouts-dir");
94
97
  }
95
- // Targeting all layout dirs in the layouts index dir.
96
- // TODO: Default to the knock project config first if present before cwd.
97
- const defaultToCwd = {
98
- abspath: runCwd,
99
- exists: true
100
- };
101
- const indexDirCtx = flags["layouts-dir"] || defaultToCwd;
98
+ const indexDirCtx = flags["layouts-dir"] || layoutsIndexDirCtx;
102
99
  return {
103
100
  type: "emailLayoutsIndexDir",
104
101
  context: indexDirCtx
@@ -109,7 +106,7 @@ const ensureValidCommandTarget = async (props, runContext)=>{
109
106
  if (resourceDirCtx && resourceDirCtx.key !== args.emailLayoutKey) {
110
107
  return _core.ux.error(`Cannot run ${commandId} \`${args.emailLayoutKey}\` inside another layout directory:\n${resourceDirCtx.key}`);
111
108
  }
112
- const targetDirPath = resourceDirCtx ? resourceDirCtx.abspath : _nodepath.resolve(runCwd, args.emailLayoutKey);
109
+ const targetDirPath = resourceDirCtx ? resourceDirCtx.abspath : _nodepath.resolve(layoutsIndexDirCtx.abspath, args.emailLayoutKey);
113
110
  const layoutDirCtx = {
114
111
  type: "email_layout",
115
112
  key: args.emailLayoutKey,
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
+ _export_star(require("./generator"), exports);
5
6
  _export_star(require("./helpers"), exports);
6
7
  _export_star(require("./processor.isomorphic"), exports);
7
8
  _export_star(require("./reader"), exports);